Skip to main content

qusql_type/
lib.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12#![cfg_attr(not(test), no_std)]
13#![forbid(unsafe_code)]
14
15//! Crate for typing SQL statements.
16//!
17//! ```
18//! use qusql_type::{schema::parse_schemas, type_statement, TypeOptions,
19//!     SQLDialect, SQLArguments, StatementType, Issues};
20//!
21//! let schemas = "
22//!     CREATE TABLE `events` (
23//!       `id` bigint(20) NOT NULL,
24//!       `user` int(11) NOT NULL,
25//!       `message` text NOT NULL
26//!     );";
27//!
28//! let mut issues = Issues::new(schemas);
29//! let schemas = parse_schemas(schemas,
30//!     &mut issues,
31//!     &TypeOptions::new().dialect(SQLDialect::MariaDB));
32//! assert!(issues.is_ok());
33//!
34//! let sql = "SELECT `id`, `user`, `message` FROM `events` WHERE `id` = ?";
35//! let mut issues = Issues::new(sql);
36//! let stmt = type_statement(&schemas, sql, &mut issues,
37//!     &TypeOptions::new().dialect(SQLDialect::MariaDB).arguments(SQLArguments::QuestionMark));
38//! assert!(issues.is_ok());
39//!
40//! match stmt {
41//!     StatementType::Select{columns, arguments} => {
42//!         assert_eq!(columns.len(), 3);
43//!         assert_eq!(arguments.len(), 1);
44//!     }
45//!     _ => panic!("Expected select statement")
46//! };
47//! ```
48//!
49//! ```
50//! use qusql_type::{
51//!     schema::parse_schemas, type_statement, TypeOptions,
52//!     SQLDialect, SQLArguments, StatementType, Issues,
53//! };
54//!
55//! let schema_sql = "
56//!     CREATE TABLE notes (
57//!         id    integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
58//!         title text    NOT NULL,
59//!         body  text
60//!     );";
61//!
62//! let opts = TypeOptions::new().dialect(SQLDialect::PostgreSQL);
63//!
64//! let mut issues = Issues::new(schema_sql);
65//! let schemas = parse_schemas(schema_sql, &mut issues, &opts);
66//! assert!(issues.is_ok());
67//!
68//! let query = "SELECT id, title, body FROM notes WHERE id = $1";
69//! let mut issues = Issues::new(query);
70//! let stmt = type_statement(
71//!     &schemas, query, &mut issues,
72//!     &TypeOptions::new()
73//!         .dialect(SQLDialect::PostgreSQL)
74//!         .arguments(SQLArguments::Dollar),
75//! );
76//! assert!(issues.is_ok());
77//!
78//! match stmt {
79//!     StatementType::Select { columns, arguments } => {
80//!         // columns[0] -> id   : i32,  not-null
81//!         // columns[1] -> title: String, not-null
82//!         // columns[2] -> body : Option<String>
83//!         // arguments[0] -> i32 (the type of `id`)
84//!         println!("{} columns, {} arguments", columns.len(), arguments.len());
85//!     }
86//!     _ => panic!("expected SELECT"),
87//! }
88//! ```
89
90extern crate alloc;
91
92use alloc::vec::Vec;
93pub use qusql_parse::{ByteToChar, Fragment, Issue, Issues, Level};
94use qusql_parse::{ParseOptions, parse_statement};
95use schema::Schemas;
96
97mod type_;
98mod type_binary_expression;
99mod type_call;
100mod type_delete;
101mod type_expression;
102mod type_function;
103mod type_insert_replace;
104mod type_lock;
105mod type_reference;
106mod type_select;
107mod type_set;
108mod type_statement;
109mod type_truncate;
110mod type_update;
111mod typer;
112
113pub mod schema;
114pub use type_::{BaseType, FullType, Type};
115pub use type_insert_replace::AutoIncrementId;
116pub use type_select::SelectTypeColumn;
117use typer::Typer;
118
119pub use qusql_parse::{SQLArguments, SQLDialect};
120
121/// Options used when typing sql or parsing a schema
122#[derive(Debug, Default, Clone)]
123pub struct TypeOptions {
124    parse_options: ParseOptions,
125    warn_unnamed_column_in_select: bool,
126    warn_duplicate_column_in_select: bool,
127}
128
129impl TypeOptions {
130    /// Produce new default options
131    pub fn new() -> Self {
132        Default::default()
133    }
134
135    /// Change what sql dialect is used
136    pub fn dialect(self, dialect: SQLDialect) -> Self {
137        Self {
138            parse_options: self.parse_options.dialect(dialect),
139            ..self
140        }
141    }
142
143    /// Change how sql arguments are supplied
144    pub fn arguments(self, arguments: SQLArguments) -> Self {
145        Self {
146            parse_options: self.parse_options.arguments(arguments),
147            ..self
148        }
149    }
150
151    /// Should we warn about unquoted identifiers
152    pub fn warn_unquoted_identifiers(self, warn_unquoted_identifiers: bool) -> Self {
153        Self {
154            parse_options: self
155                .parse_options
156                .warn_unquoted_identifiers(warn_unquoted_identifiers),
157            ..self
158        }
159    }
160
161    /// Should we warn about keywords not in ALL CAPS
162    pub fn warn_none_capital_keywords(self, warn_none_capital_keywords: bool) -> Self {
163        Self {
164            parse_options: self
165                .parse_options
166                .warn_none_capital_keywords(warn_none_capital_keywords),
167            ..self
168        }
169    }
170
171    /// Should we warn about unnamed columns in selects
172    pub fn warn_unnamed_column_in_select(self, warn_unnamed_column_in_select: bool) -> Self {
173        Self {
174            warn_unnamed_column_in_select,
175            ..self
176        }
177    }
178
179    /// Should we warn about duplicate columns in selects
180    pub fn warn_duplicate_column_in_select(self, warn_duplicate_column_in_select: bool) -> Self {
181        Self {
182            warn_duplicate_column_in_select,
183            ..self
184        }
185    }
186
187    /// Parse _LIST_ as special expression and type as a list of items
188    pub fn list_hack(self, list_hack: bool) -> Self {
189        Self {
190            parse_options: self.parse_options.list_hack(list_hack),
191            ..self
192        }
193    }
194}
195
196/// Key of argument
197#[derive(Debug, Clone, Hash, PartialEq, Eq)]
198pub enum ArgumentKey<'a> {
199    /// Index of unnamed argument
200    Index(usize),
201    /// Name of named argument
202    Identifier(&'a str),
203}
204
205/// Type information of typed statement
206#[derive(Debug, Clone)]
207pub enum StatementType<'a> {
208    /// The statement was a select statement
209    Select {
210        /// The types and named of the columns return from the select
211        columns: Vec<SelectTypeColumn<'a>>,
212        /// The key and type of arguments to the query
213        arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
214    },
215    /// The statement is a delete statement
216    Delete {
217        /// The key and type of arguments to the query
218        arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
219        /// If present, the types and names of the columns returned from the delete
220        returning: Option<Vec<SelectTypeColumn<'a>>>,
221    },
222    /// The statement is an insert statement
223    Insert {
224        /// The insert happend in a table with a auto increment id row
225        yield_autoincrement: AutoIncrementId,
226        /// The key and type of arguments to the query
227        arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
228        /// If present, the types and names of the columns returned from the insert
229        returning: Option<Vec<SelectTypeColumn<'a>>>,
230    },
231    /// The statement is a update statement
232    Update {
233        /// The key and type of arguments to the query
234        arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
235        /// If present, the types and names of the columns returned from the insert
236        returning: Option<Vec<SelectTypeColumn<'a>>>,
237    },
238    /// The statement is a replace statement
239    Replace {
240        /// The key and type of arguments to the query
241        arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
242        /// If present, the types and names of the columns returned from the replace
243        returning: Option<Vec<SelectTypeColumn<'a>>>,
244    },
245    /// The statement is a truncate statement
246    Truncate,
247    /// The statement is a call statement
248    Call {
249        /// The key and type of arguments to the query
250        arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
251    },
252    /// The statement is a transaction control statement (BEGIN, COMMIT, END, START TRANSACTION)
253    Transaction,
254    /// The statement is a set statement
255    Set,
256    /// The statement is a table lock/unlock statement
257    Lock,
258    /// The query was not valid, errors are preset in issues
259    Invalid,
260}
261
262/// Type an sql statement with respect to a given schema
263pub fn type_statement<'a>(
264    schemas: &'a Schemas<'a>,
265    statement: &'a str,
266    issues: &mut Issues<'a>,
267    options: &TypeOptions,
268) -> StatementType<'a> {
269    if let Some(stmt) = parse_statement(statement, issues, &options.parse_options) {
270        let mut typer = Typer {
271            schemas,
272            issues,
273            reference_types: Vec::new(),
274            outer_reference_types: Vec::new(),
275            arg_types: Default::default(),
276            options,
277            with_schemas: Default::default(),
278        };
279        let t = type_statement::type_statement(&mut typer, &stmt);
280        let arguments = typer.arg_types;
281        match t {
282            type_statement::InnerStatementType::Select(s) => StatementType::Select {
283                columns: s.columns,
284                arguments,
285            },
286            type_statement::InnerStatementType::Delete { returning } => StatementType::Delete {
287                arguments,
288                returning: returning.map(|r| r.columns),
289            },
290            type_statement::InnerStatementType::Insert {
291                auto_increment_id,
292                returning,
293            } => StatementType::Insert {
294                yield_autoincrement: auto_increment_id,
295                arguments,
296                returning: returning.map(|r| r.columns),
297            },
298            type_statement::InnerStatementType::Update { returning } => StatementType::Update {
299                arguments,
300                returning: returning.map(|r| r.columns),
301            },
302            type_statement::InnerStatementType::Replace { returning } => StatementType::Replace {
303                arguments,
304                returning: returning.map(|r| r.columns),
305            },
306            type_statement::InnerStatementType::Truncate => StatementType::Truncate,
307            type_statement::InnerStatementType::Call => StatementType::Call { arguments },
308            type_statement::InnerStatementType::Transaction => StatementType::Transaction,
309            type_statement::InnerStatementType::Set => StatementType::Set,
310            type_statement::InnerStatementType::Lock => StatementType::Lock,
311            type_statement::InnerStatementType::Invalid => StatementType::Invalid,
312        }
313    } else {
314        StatementType::Invalid
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use alloc::vec::Vec;
321    use codespan_reporting::{
322        diagnostic::{Diagnostic, Label},
323        files::SimpleFiles,
324        term::{
325            self,
326            termcolor::{ColorChoice, StandardStream},
327        },
328    };
329    use qusql_parse::{Identifier, Issue, Issues, Level, SQLArguments, SQLDialect};
330
331    use crate::{
332        ArgumentKey, AutoIncrementId, BaseType, FullType, SelectTypeColumn, StatementType, Type,
333        TypeOptions, schema::parse_schemas, type_statement,
334    };
335
336    struct N<'a>(Option<&'a str>);
337    impl<'a> alloc::fmt::Display for N<'a> {
338        fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
339            if let Some(v) = self.0 {
340                v.fmt(f)
341            } else {
342                f.write_str("None")
343            }
344        }
345    }
346
347    struct N2<'a>(Option<Identifier<'a>>);
348    impl<'a> alloc::fmt::Display for N2<'a> {
349        fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
350            if let Some(v) = &self.0 {
351                v.fmt(f)
352            } else {
353                f.write_str("None")
354            }
355        }
356    }
357
358    fn check_no_errors(name: &str, src: &str, issues: &[Issue], errors: &mut usize) {
359        let mut files = SimpleFiles::new();
360        let file_id = files.add(name, &src);
361        let writer = StandardStream::stderr(ColorChoice::Always);
362        let config = codespan_reporting::term::Config::default();
363        for issue in issues {
364            let mut labels = vec![Label::primary(file_id, issue.span.clone())];
365            for fragment in &issue.fragments {
366                labels.push(
367                    Label::secondary(file_id, fragment.span.clone())
368                        .with_message(fragment.message.to_string()),
369                );
370            }
371            let d = match issue.level {
372                Level::Error => {
373                    *errors += 1;
374                    Diagnostic::error()
375                }
376                Level::Warning => Diagnostic::warning(),
377            };
378            let d = d
379                .with_message(issue.message.to_string())
380                .with_labels(labels);
381            term::emit_to_write_style(&mut writer.lock(), &config, &files, &d).unwrap();
382        }
383    }
384
385    fn str_to_type(t: &str) -> FullType<'static> {
386        let (t, not_null) = if let Some(t) = t.strip_suffix('!') {
387            (t, true)
388        } else {
389            (t, false)
390        };
391        let (t, list_hack) = if let Some(v) = t.strip_suffix("[]") {
392            (v, true)
393        } else {
394            (t, false)
395        };
396        let t = match t {
397            "b" => BaseType::Bool.into(),
398            "u8" => Type::U8,
399            "u16" => Type::U16,
400            "u32" => Type::U32,
401            "u64" => Type::U64,
402            "i8" => Type::I8,
403            "i16" => Type::I16,
404            "i32" => Type::I32,
405            "i64" => Type::I64,
406            "f32" => Type::F32,
407            "f64" => Type::F64,
408            "i" => BaseType::Integer.into(),
409            "f" => BaseType::Float.into(),
410            "str" => BaseType::String.into(),
411            "bytes" => BaseType::Bytes.into(),
412            "dt" => BaseType::DateTime.into(),
413            "date" => BaseType::Date.into(),
414            "ts" => BaseType::TimeStamp.into(),
415            "time" => BaseType::Time.into(),
416            "json" => Type::JSON,
417            "any" => BaseType::Any.into(),
418            _ => panic!("Unknown type {t}"),
419        };
420        let mut t = FullType::new(t, not_null);
421        if list_hack {
422            t.list_hack = true;
423        }
424        t
425    }
426
427    fn check_arguments(
428        name: &str,
429        got: &[(ArgumentKey<'_>, FullType<'_>)],
430        expected: &str,
431        errors: &mut usize,
432    ) {
433        if expected.is_empty() {
434            for (cnt, value) in got.iter().enumerate() {
435                println!("{name}: Unexpected argument {cnt} type {value:?}");
436                *errors += 1;
437            }
438            return;
439        }
440        let mut got2 = Vec::new();
441        let inv = FullType::invalid();
442        for (k, v) in got {
443            match k {
444                ArgumentKey::Index(i) => {
445                    while got2.len() <= *i {
446                        got2.push(&inv);
447                    }
448                    got2[*i] = v;
449                }
450                ArgumentKey::Identifier(k) => {
451                    println!("{name}: Got named argument {k}");
452                    *errors += 1;
453                }
454            }
455        }
456        let mut cnt = 0;
457        for (i, t) in expected.split(',').enumerate() {
458            let t = t.trim();
459            let t = str_to_type(t);
460            if let Some(v) = got2.get(i) {
461                if *v != &t {
462                    println!("{name}: Expected type {t} for argument {i} got {v}");
463                    *errors += 1;
464                }
465            } else {
466                println!("{name}: Expected type {t} for argument {i} got None");
467                *errors += 1;
468            }
469            cnt += 1;
470        }
471        while cnt < got.len() {
472            println!("{}: Unexpected argument {} type {:?}", name, cnt, got[cnt]);
473            cnt += 1;
474            *errors += 1;
475        }
476    }
477
478    fn check_columns(
479        name: &str,
480        src: &str,
481        got: &[SelectTypeColumn<'_>],
482        expected: &str,
483        errors: &mut usize,
484    ) {
485        let mut cnt = 0;
486        for (i, t) in expected.split(',').enumerate() {
487            let t = t.trim();
488            let (cname, t) = t.split_once(":").unwrap();
489            let t = str_to_type(t);
490            let cname = if cname.is_empty() { None } else { Some(cname) };
491            if let Some(v) = got.get(i) {
492                if v.name.as_deref() != cname || v.type_ != t {
493                    let mut files = SimpleFiles::new();
494                    let file_id = files.add(name, &src);
495                    let writer = StandardStream::stderr(ColorChoice::Always);
496                    let config = codespan_reporting::term::Config::default();
497                    let d = Diagnostic::error()
498                        .with_message(format!(
499                            "{}: Expected column {} with name {} of type {} got {} of type {}",
500                            name,
501                            i,
502                            N(cname),
503                            t,
504                            N2(v.name.clone()),
505                            v.type_
506                        ))
507                        .with_label(Label::primary(file_id, v.span.clone()));
508
509                    term::emit_to_write_style(&mut writer.lock(), &config, &files, &d).unwrap();
510
511                    *errors += 1;
512                }
513            } else {
514                let mut files = SimpleFiles::new();
515                let file_id = files.add(name, &src);
516                let writer = StandardStream::stderr(ColorChoice::Always);
517                let config = codespan_reporting::term::Config::default();
518                let d = Diagnostic::error()
519                    .with_message(format!(
520                        "{}: Expected column {} with name {} of type {} got None",
521                        name,
522                        i,
523                        N(cname),
524                        t
525                    ))
526                    .with_label(Label::primary(file_id, 0..src.len()));
527                term::emit_to_write_style(&mut writer.lock(), &config, &files, &d).unwrap();
528                *errors += 1;
529            }
530            cnt += 1;
531        }
532        while cnt < got.len() {
533            println!(
534                "{}: Unexpected column {} with name {} of type {}",
535                name,
536                cnt,
537                N2(got[cnt].name.clone()),
538                got[cnt].type_
539            );
540            cnt += 1;
541            *errors += 1;
542        }
543    }
544
545    #[test]
546    fn mariadb() {
547        let schema_src = "
548
549        DROP TABLE IF EXISTS `t1`;
550        CREATE TABLE `t1` (
551          `id` int(11) NOT NULL,
552          `cbool` tinyint(1) NOT NULL,
553          `cu8` tinyint UNSIGNED NOT NULL,
554          `cu16` smallint UNSIGNED NOT NULL,
555          `cu32` int UNSIGNED NOT NULL,
556          `cu64` bigint UNSIGNED NOT NULL,
557          `ci8` tinyint,
558          `ci16` smallint,
559          `ci32` int,
560          `ci64` bigint,
561          `cbin` binary(16),
562          `ctext` varchar(100) NOT NULL,
563          `cbytes` blob,
564          `cf32` float,
565          `cf64` double,
566          `cu8_plus_one` tinyint UNSIGNED GENERATED ALWAYS AS (
567            `cu8` + 1
568           ) STORED,
569          `status` varchar(10) GENERATED ALWAYS AS (case when `cu8` <> 0 and `cu16` = 0 then 'a' when
570            `cbool` then 'b' when `ci32` = 42 then 'd' when `cu64` = 43 then 'x' when
571            `ci64` = 12 then 'y' else 'z' end) VIRTUAL
572        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
573
574        ALTER TABLE `t1`
575          MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
576
577        DROP INDEX IF EXISTS `hat` ON `t1`;
578
579        CREATE INDEX `hat2` ON `t1` (`id`, `cf64`);
580
581        CREATE TABLE `t2` (
582          `id` int(11) NOT NULL AUTO_INCREMENT,
583          `t1_id` int(11) NOT NULL);
584
585        CREATE TABLE `t3` (
586            `id` int(11) NOT NULL AUTO_INCREMENT,
587            `text` TEXT);
588
589        CREATE TABLE `t4` (
590            `id` int(11) NOT NULL AUTO_INCREMENT,
591            `dt` datetime NOT NULL);
592
593        CREATE TABLE `t5` (
594            `id` int(11) NOT NULL AUTO_INCREMENT,
595            `a` int NOT NULL,
596            `b` int,
597            `c` int NOT NULL DEFAULT 42);
598
599        CREATE TABLE `t6` (
600            `id` int(11) NOT NULL AUTO_INCREMENT,
601            `d` date NOT NULL,
602            `dt` datetime NOT NULL,
603            `t` time NOT NULL);
604
605        CREATE PROCEDURE `p1`(IN `a` int, IN `b` varchar(100))
606        BEGIN
607        END;
608        ";
609
610        let options = TypeOptions::new().dialect(SQLDialect::MariaDB);
611        let mut issues = Issues::new(schema_src);
612        let schema = parse_schemas(schema_src, &mut issues, &options);
613        let mut errors = 0;
614        check_no_errors("schema", schema_src, issues.get(), &mut errors);
615
616        let options = TypeOptions::new()
617            .dialect(SQLDialect::MariaDB)
618            .arguments(SQLArguments::QuestionMark);
619
620        {
621            let name = "q1";
622            let src =
623                "SELECT `id`, `cbool`, `cu8`, `cu8_plus_one`, `cu16`, `cu32`, `cu64`, `ci8`, `ci16`, `ci32`, `ci64`,
624                `ctext`, `cbytes`, `cf32`, `cf64` FROM `t1` WHERE ci8 IS NOT NULL
625                AND `cbool`=? AND `cu8`=? AND `cu16`=? AND `cu32`=? AND `cu64`=?
626                AND `ci8`=? AND `ci16`=? AND `ci32`=? AND `ci64`=?
627                AND `ctext`=? AND `cbytes`=? AND `cf32`=? AND `cf64`=?";
628
629            let mut issues: Issues<'_> = Issues::new(src);
630            let q = type_statement(&schema, src, &mut issues, &options);
631            check_no_errors(name, src, issues.get(), &mut errors);
632            if let StatementType::Select { arguments, columns } = q {
633                check_arguments(
634                    name,
635                    &arguments,
636                    "b,i,i,i,i,i,i,i,i,str,bytes,f,f",
637                    &mut errors,
638                );
639                check_columns(
640                    name,
641                    src,
642                    &columns,
643                    "id:i32!,cbool:b!,cu8:u8!,cu8_plus_one:u8!,cu16:u16!,cu32:u32!,cu64:u64!,
644                    ci8:i8!,ci16:i16!,ci32:i32!,ci64:i64!,ctext:str!,cbytes:bytes!,cf32:f32!,cf64:f64!",
645                    &mut errors,
646                );
647            } else {
648                println!("{name} should be select");
649                errors += 1;
650            }
651        }
652
653        {
654            let name = "q1.1";
655            let src =
656                "SELECT `id`, `cbool`, `cu8`, `cu16`, `cu32`, `cu64`, `ci8`, `ci16`, `ci32`, `ci64`,
657                `ctext`, `cbytes`, `cf32`, `cf64`, `cbin` FROM `t1` WHERE ci8 IS NOT NULL";
658
659            let mut issues: Issues<'_> = Issues::new(src);
660            let q = type_statement(&schema, src, &mut issues, &options);
661            check_no_errors(name, src, issues.get(), &mut errors);
662            if let StatementType::Select { arguments, columns } = q {
663                check_arguments(name, &arguments, "", &mut errors);
664                check_columns(
665                    name,
666                    src,
667                    &columns,
668                    "id:i32!,cbool:b!,cu8:u8!,cu16:u16!,cu32:u32!,cu64:u64!,
669                    ci8:i8!,ci16:i16,ci32:i32,ci64:i64,ctext:str!,cbytes:bytes,cf32:f32,cf64:f64,cbin:bytes",
670                    &mut errors,
671                );
672            } else {
673                println!("{name} should be select");
674                errors += 1;
675            }
676        }
677
678        {
679            let name = "q2";
680            let src =
681            "INSERT INTO `t1` (`cbool`, `cu8`, `cu16`, `cu32`, `cu64`, `ci8`, `ci16`, `ci32`, `ci64`,
682            `ctext`, `cbytes`, `cf32`, `cf64`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
683
684            let mut issues: Issues<'_> = Issues::new(src);
685            let q = type_statement(&schema, src, &mut issues, &options);
686            check_no_errors(name, src, issues.get(), &mut errors);
687            if let StatementType::Insert {
688                arguments,
689                yield_autoincrement,
690                returning,
691            } = q
692            {
693                check_arguments(
694                    name,
695                    &arguments,
696                    "b!,u8!,u16!,u32!,u64!,i8,i16,i32,i64,str!,bytes,f32,f64",
697                    &mut errors,
698                );
699                if yield_autoincrement != AutoIncrementId::Yes {
700                    println!("{name} should yield autoincrement");
701                    errors += 1;
702                }
703                if returning.is_some() {
704                    println!("{name} should not return columns");
705                    errors += 1;
706                }
707            } else {
708                println!("{name} should be insert");
709                errors += 1;
710            }
711        }
712
713        {
714            let name = "q3";
715            let src =
716                "DELETE `t1` FROM `t1`, `t2` WHERE `t1`.`id` = `t2`.`t1_id` AND `t2`.`id` = ?";
717            let mut issues: Issues<'_> = Issues::new(src);
718            let q = type_statement(&schema, src, &mut issues, &options);
719            check_no_errors(name, src, issues.get(), &mut errors);
720            if let StatementType::Delete { arguments, .. } = q {
721                check_arguments(name, &arguments, "i", &mut errors);
722            } else {
723                println!("{name} should be delete");
724                errors += 1;
725            }
726        }
727
728        {
729            let name = "q4";
730            let src = "INSERT INTO `t2` (`t1_id`) VALUES (?) ON DUPLICATE KEY UPDATE `t1_id`=?";
731            let mut issues: Issues<'_> = Issues::new(src);
732            let q = type_statement(&schema, src, &mut issues, &options);
733            check_no_errors(name, src, issues.get(), &mut errors);
734            if let StatementType::Insert {
735                arguments,
736                yield_autoincrement,
737                returning,
738            } = q
739            {
740                check_arguments(name, &arguments, "i32!,i32!", &mut errors);
741                if yield_autoincrement != AutoIncrementId::Optional {
742                    println!("{name} should yield optional auto increment");
743                    errors += 1;
744                }
745                if returning.is_some() {
746                    println!("{name} should not return columns");
747                    errors += 1;
748                }
749            } else {
750                println!("{name} should be insert");
751                errors += 1;
752            }
753        }
754
755        {
756            let name = "q5";
757            let src = "INSERT IGNORE INTO `t2` SET `t1_id`=?";
758            let mut issues: Issues<'_> = Issues::new(src);
759            let q = type_statement(&schema, src, &mut issues, &options);
760            check_no_errors(name, src, issues.get(), &mut errors);
761            if let StatementType::Insert {
762                arguments,
763                yield_autoincrement,
764                returning,
765            } = q
766            {
767                check_arguments(name, &arguments, "i32!", &mut errors);
768                if yield_autoincrement != AutoIncrementId::Optional {
769                    println!("{name} should yield optional auto increment");
770                    errors += 1;
771                }
772                if returning.is_some() {
773                    println!("{name} should not return columns");
774                    errors += 1;
775                }
776            } else {
777                println!("{name} should be insert");
778                errors += 1;
779            }
780        }
781
782        {
783            let name = "q6";
784            let src = "SELECT IF(`ci32` IS NULL, `cbool`, ?) AS `cc` FROM `t1`";
785            let mut issues: Issues<'_> = Issues::new(src);
786            let q = type_statement(&schema, src, &mut issues, &options);
787            check_no_errors(name, src, issues.get(), &mut errors);
788            if let StatementType::Select { arguments, columns } = q {
789                check_arguments(name, &arguments, "b", &mut errors);
790                check_columns(name, src, &columns, "cc:b", &mut errors);
791            } else {
792                println!("{name} should be select");
793                errors += 1;
794            }
795        }
796
797        {
798            let name = "q7";
799            let src = "SELECT FROM_UNIXTIME(CAST(UNIX_TIMESTAMP() AS DOUBLE)) AS `cc` FROM `t1` WHERE `id`=?";
800            let mut issues: Issues<'_> = Issues::new(src);
801            let q = type_statement(&schema, src, &mut issues, &options);
802            check_no_errors(name, src, issues.get(), &mut errors);
803            if let StatementType::Select { arguments, columns } = q {
804                check_arguments(name, &arguments, "i", &mut errors);
805                check_columns(name, src, &columns, "cc:dt!", &mut errors);
806            } else {
807                println!("{name} should be select");
808                errors += 1;
809            }
810        }
811
812        {
813            let name = "q8";
814            let src = "REPLACE INTO `t2` SET `id` = ?, `t1_id`=?";
815            let mut issues: Issues<'_> = Issues::new(src);
816            let q = type_statement(&schema, src, &mut issues, &options);
817            check_no_errors(name, src, issues.get(), &mut errors);
818            if let StatementType::Replace {
819                arguments,
820                returning,
821            } = q
822            {
823                check_arguments(name, &arguments, "i32!,i32!", &mut errors);
824                if returning.is_some() {
825                    println!("{name} should not return columns");
826                    errors += 1;
827                }
828            } else {
829                println!("{name} should be replace");
830                errors += 1;
831            }
832        }
833
834        {
835            let name = "q9";
836            let src = "INSERT INTO `t2` (`t1_id`) VALUES (32) ON DUPLICATE KEY UPDATE `t1_id` = `t1_id` + VALUES(`t1_id`)";
837            let mut issues: Issues<'_> = Issues::new(src);
838            let q = type_statement(&schema, src, &mut issues, &options);
839            check_no_errors(name, src, issues.get(), &mut errors);
840            if let StatementType::Insert { arguments, .. } = q {
841                check_arguments(name, &arguments, "", &mut errors);
842            } else {
843                println!("{name} should be insert");
844                errors += 1;
845            }
846        }
847
848        {
849            let name = "q10";
850            let src =
851                "SELECT SUBSTRING_INDEX(`text`, '/', 5) AS `k` FROM `t3` WHERE `text` LIKE '%T%'";
852            let mut issues: Issues<'_> = Issues::new(src);
853            let q = type_statement(&schema, src, &mut issues, &options);
854            check_no_errors(name, src, issues.get(), &mut errors);
855            if let StatementType::Select { arguments, columns } = q {
856                check_arguments(name, &arguments, "", &mut errors);
857                check_columns(name, src, &columns, "k:str!", &mut errors);
858            } else {
859                println!("{name} should be select");
860                errors += 1;
861            }
862        }
863
864        {
865            let name = "q11";
866            let src = "SELECT * FROM `t1`, `t2` LEFT JOIN `t3` ON `t3`.`id` = `t1`.`id`";
867            let mut issues: Issues<'_> = Issues::new(src);
868            type_statement(&schema, src, &mut issues, &options);
869            if !issues.get().iter().any(|i| i.level == Level::Error) {
870                println!("{name} should be an error");
871                errors += 1;
872            }
873        }
874
875        {
876            let name = "q11b";
877            let src = "SELECT 1 FROM `t3` WHERE `text` LIKE '%T%' ESCAPE 'ab'";
878            let mut issues: Issues<'_> = Issues::new(src);
879            type_statement(&schema, src, &mut issues, &options);
880            if !issues.get().iter().any(|i| i.level == Level::Error) {
881                println!("{name} should be an error (ESCAPE must be one character)");
882                errors += 1;
883            }
884        }
885
886        {
887            let name = "q12";
888            let src = "SELECT JSON_REPLACE('{ \"A\": 1, \"B\": [2, 3]}', '$.B[1]', 4, '$.C[3]', 3) AS `k` FROM `t3`";
889            let mut issues: Issues<'_> = Issues::new(src);
890            let q = type_statement(&schema, src, &mut issues, &options);
891            check_no_errors(name, src, issues.get(), &mut errors);
892            if let StatementType::Select { arguments, columns } = q {
893                check_arguments(name, &arguments, "", &mut errors);
894                check_columns(name, src, &columns, "k:json", &mut errors);
895            } else {
896                println!("{name} should be select");
897                errors += 1;
898            }
899        }
900
901        {
902            let options = options.clone().list_hack(true);
903            let name = "q13";
904            let src = "SELECT `id` FROM `t1` WHERE `id` IN (_LIST_)";
905            let mut issues: Issues<'_> = Issues::new(src);
906            let q = type_statement(&schema, src, &mut issues, &options);
907            check_no_errors(name, src, issues.get(), &mut errors);
908            if let StatementType::Select { arguments, columns } = q {
909                check_arguments(name, &arguments, "i[]", &mut errors);
910                check_columns(name, src, &columns, "id:i32!", &mut errors);
911            } else {
912                println!("{name} should be select");
913                errors += 1;
914            }
915        }
916
917        {
918            let name = "q14";
919            let src = "SELECT CAST(NULL AS CHAR) AS `id`";
920            let mut issues: Issues<'_> = Issues::new(src);
921            let q = type_statement(&schema, src, &mut issues, &options);
922            check_no_errors(name, src, issues.get(), &mut errors);
923            if let StatementType::Select { arguments, columns } = q {
924                check_arguments(name, &arguments, "", &mut errors);
925                check_columns(name, src, &columns, "id:str", &mut errors);
926            } else {
927                println!("{name} should be select");
928                errors += 1;
929            }
930        }
931
932        {
933            let name = "q15";
934            let src =
935				"INSERT INTO `t1` (`cbool`, `cu8`, `cu16`, `cu32`, `cu64`, `ci8`, `ci16`, `ci32`, `ci64`,
936            `ctext`, `cbytes`, `cf32`, `cf64`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
937                 RETURNING `id`, `cbool`, `cu8`, `ctext`, `cf64`";
938            let mut issues: Issues<'_> = Issues::new(src);
939            let q = type_statement(&schema, src, &mut issues, &options);
940            check_no_errors(name, src, issues.get(), &mut errors);
941            if let StatementType::Insert {
942                arguments,
943                yield_autoincrement,
944                returning,
945            } = q
946            {
947                check_arguments(
948                    name,
949                    &arguments,
950                    "b!,u8!,u16!,u32!,u64!,i8,i16,i32,i64,str!,bytes,f32,f64",
951                    &mut errors,
952                );
953                if yield_autoincrement != AutoIncrementId::Yes {
954                    println!("{name} should yield autoincrement");
955                    errors += 1;
956                }
957                if let Some(returning) = returning {
958                    check_columns(
959                        name,
960                        src,
961                        &returning,
962                        "id:i32!,cbool:b!,cu8:u8!,ctext:str!,cf64:f64",
963                        &mut errors,
964                    );
965                } else {
966                    println!("{name} should return columns");
967                    errors += 1;
968                }
969            } else {
970                println!("{name} should be insert");
971                errors += 1;
972            }
973        }
974
975        {
976            let name = "q16";
977            let src = "REPLACE INTO `t2` SET `id` = ?, `t1_id`=? RETURNING `id`";
978            let mut issues: Issues<'_> = Issues::new(src);
979            let q = type_statement(&schema, src, &mut issues, &options);
980            check_no_errors(name, src, issues.get(), &mut errors);
981            if let StatementType::Replace {
982                arguments,
983                returning,
984            } = q
985            {
986                check_arguments(name, &arguments, "i32!,i32!", &mut errors);
987                if let Some(returning) = returning {
988                    check_columns(name, src, &returning, "id:i32!", &mut errors);
989                } else {
990                    println!("{name} should return columns");
991                    errors += 1;
992                }
993            } else {
994                println!("{name} should be replace");
995                errors += 1;
996            }
997        }
998
999        {
1000            let name = "q17";
1001            let src = "SELECT dt, UNIX_TIMESTAMP(dt) AS t FROM t4";
1002            let mut issues: Issues<'_> = Issues::new(src);
1003            let q = type_statement(&schema, src, &mut issues, &options);
1004            check_no_errors(name, src, issues.get(), &mut errors);
1005            if let StatementType::Select { arguments, columns } = q {
1006                check_arguments(name, &arguments, "", &mut errors);
1007                check_columns(name, src, &columns, "dt:dt!,t:i64!", &mut errors);
1008            } else {
1009                println!("{name} should be select");
1010                errors += 1;
1011            }
1012        }
1013
1014        {
1015            let name = "q17";
1016            let src = "SELECT CONCAT(?, \"hat\") AS c";
1017            let mut issues: Issues<'_> = Issues::new(src);
1018            let q = type_statement(&schema, src, &mut issues, &options);
1019            check_no_errors(name, src, issues.get(), &mut errors);
1020            if let StatementType::Select { arguments, columns } = q {
1021                check_arguments(name, &arguments, "any", &mut errors);
1022                check_columns(name, src, &columns, "c:str", &mut errors);
1023            } else {
1024                println!("{name} should be selsect");
1025                errors += 1;
1026            }
1027        }
1028
1029        {
1030            let name = "q18";
1031            let src = "SELECT CAST(\"::0\" AS INET6) AS `id`";
1032            let mut issues: Issues<'_> = Issues::new(src);
1033            let q = type_statement(&schema, src, &mut issues, &options);
1034            check_no_errors(name, src, issues.get(), &mut errors);
1035            if let StatementType::Select { arguments, columns } = q {
1036                check_arguments(name, &arguments, "", &mut errors);
1037                check_columns(name, src, &columns, "id:str!", &mut errors);
1038            } else {
1039                println!("{name} should be select");
1040                errors += 1;
1041            }
1042        }
1043
1044        {
1045            let name: &str = "q18";
1046            let src = "SELECT SUBSTRING(`cbytes`, 1, 5) AS `k` FROM `t1`";
1047            let mut issues: Issues<'_> = Issues::new(src);
1048            let q = type_statement(&schema, src, &mut issues, &options);
1049            check_no_errors(name, src, issues.get(), &mut errors);
1050            if let StatementType::Select { arguments, columns } = q {
1051                check_arguments(name, &arguments, "", &mut errors);
1052                check_columns(name, src, &columns, "k:bytes", &mut errors);
1053            } else {
1054                println!("{name} should be select");
1055                errors += 1;
1056            }
1057        }
1058
1059        {
1060            let name = "q19";
1061            let src = "SELECT SUBSTRING(`ctext`, 1, 5) AS `k` FROM `t1`";
1062            let mut issues: Issues<'_> = Issues::new(src);
1063            let q = type_statement(&schema, src, &mut issues, &options);
1064            check_no_errors(name, src, issues.get(), &mut errors);
1065            if let StatementType::Select { arguments, columns } = q {
1066                check_arguments(name, &arguments, "", &mut errors);
1067                check_columns(name, src, &columns, "k:str!", &mut errors);
1068            } else {
1069                println!("{name} should be select");
1070                errors += 1;
1071            }
1072        }
1073
1074        {
1075            let name = "q19";
1076            let src = "SELECT SUBSTRING(`ctext`, 1, 5) AS `k` FROM `t1`";
1077            let mut issues: Issues<'_> = Issues::new(src);
1078            let q = type_statement(&schema, src, &mut issues, &options);
1079            check_no_errors(name, src, issues.get(), &mut errors);
1080            if let StatementType::Select { arguments, columns } = q {
1081                check_arguments(name, &arguments, "", &mut errors);
1082                check_columns(name, src, &columns, "k:str!", &mut errors);
1083            } else {
1084                println!("{name} should be select");
1085                errors += 1;
1086            }
1087        }
1088
1089        {
1090            let name = "q20";
1091            let src = "SELECT JSON_QUERY('{ \"A\": 1, \"B\": [2, 3]}', '$.B[1]') AS `k` FROM `t3`";
1092            let mut issues: Issues<'_> = Issues::new(src);
1093            let q = type_statement(&schema, src, &mut issues, &options);
1094            check_no_errors(name, src, issues.get(), &mut errors);
1095            if let StatementType::Select { arguments, columns } = q {
1096                check_arguments(name, &arguments, "", &mut errors);
1097                check_columns(name, src, &columns, "k:json", &mut errors);
1098            } else {
1099                println!("{name} should be select");
1100                errors += 1;
1101            }
1102        }
1103
1104        {
1105            let name = "q21";
1106            let src = "SELECT JSON_REMOVE('{ \"A\": 1, \"B\": [2, 3]}', '$.B[1]', '$.C[3]') AS `k` FROM `t3`";
1107            let mut issues: Issues<'_> = Issues::new(src);
1108            let q = type_statement(&schema, src, &mut issues, &options);
1109            check_no_errors(name, src, issues.get(), &mut errors);
1110            if let StatementType::Select { arguments, columns } = q {
1111                check_arguments(name, &arguments, "", &mut errors);
1112                check_columns(name, src, &columns, "k:json", &mut errors);
1113            } else {
1114                println!("{name} should be select");
1115                errors += 1;
1116            }
1117        }
1118
1119        {
1120            let name = "q22";
1121            let src = "SELECT JSON_OVERLAPS('false', 'false') AS `k` FROM `t3`";
1122            let mut issues: Issues<'_> = Issues::new(src);
1123            let q = type_statement(&schema, src, &mut issues, &options);
1124            check_no_errors(name, src, issues.get(), &mut errors);
1125            if let StatementType::Select { arguments, columns } = q {
1126                check_arguments(name, &arguments, "", &mut errors);
1127                check_columns(name, src, &columns, "k:b!", &mut errors);
1128            } else {
1129                println!("{name} should be select");
1130                errors += 1;
1131            }
1132        }
1133
1134        {
1135            let name = "q23";
1136            let src = "SELECT JSON_OVERLAPS('false', NULL) AS `k` FROM `t3`";
1137            let mut issues: Issues<'_> = Issues::new(src);
1138            let q = type_statement(&schema, src, &mut issues, &options);
1139            check_no_errors(name, src, issues.get(), &mut errors);
1140            if let StatementType::Select { arguments, columns } = q {
1141                check_arguments(name, &arguments, "", &mut errors);
1142                check_columns(name, src, &columns, "k:b", &mut errors);
1143            } else {
1144                println!("{name} should be select");
1145                errors += 1;
1146            }
1147        }
1148
1149        {
1150            let name = "q24";
1151            let src = "SELECT JSON_CONTAINS('{\"A\": 0, \"B\": [\"x\", \"y\"]}', '\"x\"', '$.B') AS `k` FROM `t3`";
1152            let mut issues: Issues<'_> = Issues::new(src);
1153            let q = type_statement(&schema, src, &mut issues, &options);
1154            check_no_errors(name, src, issues.get(), &mut errors);
1155            if let StatementType::Select { arguments, columns } = q {
1156                check_arguments(name, &arguments, "", &mut errors);
1157                check_columns(name, src, &columns, "k:b!", &mut errors);
1158            } else {
1159                println!("{name} should be select");
1160                errors += 1;
1161            }
1162        }
1163
1164        {
1165            let name = "q25";
1166            let src = "SELECT JSON_CONTAINS('{\"A\": 0, \"B\": [\"x\", \"y\"]}', NULL, '$.A') AS `k` FROM `t3`";
1167            let mut issues: Issues<'_> = Issues::new(src);
1168            let q = type_statement(&schema, src, &mut issues, &options);
1169            check_no_errors(name, src, issues.get(), &mut errors);
1170            if let StatementType::Select { arguments, columns } = q {
1171                check_arguments(name, &arguments, "", &mut errors);
1172                check_columns(name, src, &columns, "k:b", &mut errors);
1173            } else {
1174                println!("{name} should be select");
1175                errors += 1;
1176            }
1177        }
1178
1179        {
1180            let name = "q26";
1181            let src = "SELECT `id` FROM `t1` FORCE INDEX (`hat`)";
1182            let mut issues: Issues<'_> = Issues::new(src);
1183            type_statement(&schema, src, &mut issues, &options);
1184            if issues.is_ok() {
1185                println!("{name} should fail");
1186                errors += 1;
1187            }
1188        }
1189
1190        {
1191            let name = "q27";
1192            let src = "SELECT `id` FROM `t1` USE INDEX (`hat2`)";
1193            let mut issues: Issues<'_> = Issues::new(src);
1194            let q = type_statement(&schema, src, &mut issues, &options);
1195            check_no_errors(name, src, issues.get(), &mut errors);
1196            if let StatementType::Select { arguments, columns } = q {
1197                check_arguments(name, &arguments, "", &mut errors);
1198                check_columns(name, src, &columns, "id:i32!", &mut errors);
1199            } else {
1200                println!("{name} should be select");
1201                errors += 1;
1202            }
1203        }
1204
1205        {
1206            let name = "q28";
1207            let src = "INSERT INTO t5 (`a`) VALUES (44)";
1208            check_no_errors(name, src, issues.get(), &mut errors);
1209        }
1210
1211        {
1212            let name = "q29";
1213            let src = "INSERT INTO t5 (`a`, `b`, `c`) VALUES (?, ?)";
1214            let mut issues: Issues<'_> = Issues::new(src);
1215            type_statement(&schema, src, &mut issues, &options);
1216            if issues.is_ok() {
1217                println!("{name} should fail");
1218                errors += 1;
1219            }
1220        }
1221
1222        {
1223            let name = "q30";
1224            let src = "INSERT INTO t5 (`a`, `b`, `c`) VALUES (?, ?, ?)";
1225            check_no_errors(name, src, issues.get(), &mut errors);
1226        }
1227
1228        {
1229            let name = "q31";
1230            let src = "INSERT INTO t5 (`a`, `b`, `c`) VALUES (?, ?, ?, ?)";
1231            let mut issues: Issues<'_> = Issues::new(src);
1232            type_statement(&schema, src, &mut issues, &options);
1233            if issues.is_ok() {
1234                println!("{name} should fail");
1235                errors += 1;
1236            }
1237        }
1238
1239        {
1240            let name = "q32";
1241            let src = "INSERT INTO t5 (`b`, `c`) VALUES (44, 45)";
1242            let mut issues: Issues<'_> = Issues::new(src);
1243            type_statement(&schema, src, &mut issues, &options);
1244            if issues.is_ok() {
1245                println!("{name} should fail");
1246                errors += 1;
1247            }
1248        }
1249
1250        {
1251            let mut t = |expr: &str, t: &str| {
1252                let name = format!("q33 {expr}");
1253                let src = format!("SELECT {expr} AS q FROM t6");
1254                let mut issues: Issues<'_> = Issues::new(&src);
1255                let q = type_statement(&schema, &src, &mut issues, &options);
1256                check_no_errors(&name, &src, issues.get(), &mut errors);
1257                if let StatementType::Select { columns, .. } = q {
1258                    check_columns(&name, &src, &columns, &format!("q:{t}"), &mut errors);
1259                } else {
1260                    println!("{name} should be select");
1261                    errors += 1;
1262                }
1263            };
1264            t("ADD_MONTHS(`d`, 2)", "date!");
1265            t("DATE_ADD(`d`, INTERVAL 31 DAY)", "date");
1266            t("ADDDATE(`d`, 31)", "date");
1267            // t("ADDTIME(`dt`, '1 1:1:1.000002')", "dt!");
1268            // t("ADDTIME(`t`, '02:00:00.999998')", "time!");
1269            t("CONVERT_TZ(`dt`, '+00:00','+10:00')", "dt!");
1270            //t("CURDATE() + 0", "i!");
1271            t("CURDATE()", "date!");
1272            t("CURDATE() - INTERVAL 5 DAY", "date!");
1273            // t("CURTIME() + 0.0", "f!");
1274            t("CURTIME()", "time!");
1275            t("CURTIME()", "time!");
1276            t("DATE('2013-07-18 12:21:32')", "date!");
1277            t("`dt` + INTERVAL 1 SECOND", "dt!");
1278            t("INTERVAL 1 DAY + `d`", "date!");
1279            t("DATE_ADD(`dt`, INTERVAL 1 SECOND)", "dt");
1280            t("DATE_ADD(`dt`, INTERVAL '1:1' MINUTE_SECOND)", "dt");
1281            t("DATE_FORMAT(`dt`, '%D %y %a %d %m %b %j')", "str!");
1282            t("DATE_SUB(`d`, INTERVAL 31 DAY)", "date");
1283            t("DATE_SUB(`dt`, INTERVAL '1 1:1:1' DAY_SECOND)", "dt");
1284            t("DATEDIFF(`dt`, `d`)", "i!");
1285            t("DAYNAME(`d`)", "str!");
1286            t("DAYOFMONTH(`d`)", "i!");
1287            t("DAYOFWEEK(`d`)", "i!");
1288            t("DAYOFYEAR(`d`)", "i!");
1289            t("EXTRACT(DAY_MINUTE FROM `dt`)", "i!");
1290            t("FROM_DAYS(730669)", "date!");
1291            t("FROM_UNIXTIME(1196440219.0)", "dt!");
1292            // t("FROM_UNIXTIME(1196440219.0) + 0.0", "f!");
1293            t(
1294                "FROM_UNIXTIME(1196440219.0, '%Y %D %M %h:%i:%s %x')",
1295                "str!",
1296            );
1297            t("HOUR(`t`)", "i!");
1298            t("LAST_DAY('2004-01-01 01:01:01')", "date!");
1299            t("MAKEDATE(2011,31)", "date");
1300            t("MAKETIME(13,57,33)", "time");
1301            t("MICROSECOND(`dt`)", "i!");
1302            t("MINUTE(`dt`)", "i!");
1303            t("MONTH(`d`)", "i!");
1304            t("MONTHNAME(`dt`)", "str!");
1305            t("NOW()", "dt!");
1306            // t("NOW() + 0.0", "f!");
1307            t("PERIOD_ADD(200801,2)", "i!");
1308            t("PERIOD_DIFF(200802,200703)", "i!");
1309            t("QUARTER(`dt`)", "i!");
1310            // t("SEC_TO_TIME(12414)+0", "i!");
1311            t("SEC_TO_TIME(12414)", "time!");
1312            t("SECOND(`dt`)", "i!");
1313            t(
1314                "STR_TO_DATE('Wednesday23423, June 2, 2014', '%W, %M %e, %Y')",
1315                "dt!",
1316            );
1317            //t("SUBTIME(`dt`,'1 1:1:1.000002')", "dt");
1318            //t("SUBTIME(`t`, '02:00:00.999998')", "time");
1319            t("SYSDATE()", "dt!");
1320            t("TIME('2013-07-18 12:21:32')", "time!");
1321            t("TIME_FORMAT(`t`, '%H %k %h %I %l')", "str!");
1322            t("TIME_TO_SEC(`t`)", "f!");
1323            t(
1324                "TIMEDIFF('2000:01:01 00:00:00', '2000:01:01 00:00:00.000001')",
1325                "time!",
1326            );
1327            t("TIMESTAMP('2003-12-31')", "dt!");
1328            t("TIMESTAMP('2003-12-31 12:00:00','6:30:00')", "dt!");
1329            t("TIMESTAMPADD(MINUTE,1,`d`)", "dt!");
1330            t("TIMESTAMPDIFF(MONTH,'2003-02-01','2003-05-01')", "i!");
1331            t("TO_DAYS(`d`)", "i!");
1332            t("TO_SECONDS(`dt`)", "i!");
1333            t("UNIX_TIMESTAMP(`dt`)", "i64!");
1334            t("UNIX_TIMESTAMP()", "i64!");
1335            // t("UTC_DATE() + 0", "i!");
1336            t("UTC_DATE()", "date!");
1337            // t("UTC_TIME() + 0", "f!");
1338            t("UTC_TIME()", "time!");
1339            // t("UTC_TIMESTAMP() + 0", "f!");
1340            t("UTC_TIMESTAMP()", "dt!");
1341            t("WEEK(`d`)", "i!");
1342            t("WEEK(`d`, 3)", "i!");
1343            t("WEEKDAY(`d`)", "i!");
1344            t("YEAR(`d`)", "i!");
1345            t("YEARWEEK(`d`)", "i!");
1346            t("YEARWEEK(`d`, 3)", "i!");
1347        }
1348        {
1349            let name = "q34";
1350            let src = "TRUNCATE TABLE `t1`";
1351            let mut issues: Issues<'_> = Issues::new(src);
1352            let q = type_statement(&schema, src, &mut issues, &options);
1353            check_no_errors(name, src, issues.get(), &mut errors);
1354            if !matches!(q, StatementType::Truncate) {
1355                println!("{name} should be truncate");
1356                errors += 1;
1357            }
1358        }
1359
1360        {
1361            let name = "q35";
1362            let src = "TRUNCATE TABLE `unknown_table`";
1363            let mut issues: Issues<'_> = Issues::new(src);
1364            type_statement(&schema, src, &mut issues, &options);
1365            if issues.is_ok() {
1366                println!("{name} should fail");
1367                errors += 1;
1368            }
1369        }
1370
1371        {
1372            let name = "q36";
1373            let src = "CALL p1(42, 'hello')";
1374            let mut issues: Issues<'_> = Issues::new(src);
1375            let q = type_statement(&schema, src, &mut issues, &options);
1376            check_no_errors(name, src, issues.get(), &mut errors);
1377            if !matches!(q, StatementType::Call { .. }) {
1378                println!("{name} should be call");
1379                errors += 1;
1380            }
1381        }
1382
1383        {
1384            let name = "q37";
1385            let src = "CALL p1(42)";
1386            let mut issues: Issues<'_> = Issues::new(src);
1387            type_statement(&schema, src, &mut issues, &options);
1388            if issues.is_ok() {
1389                println!("{name} should fail (wrong arg count)");
1390                errors += 1;
1391            }
1392        }
1393
1394        {
1395            let name = "q38";
1396            let src = "CALL unknown_proc(1)";
1397            let mut issues: Issues<'_> = Issues::new(src);
1398            type_statement(&schema, src, &mut issues, &options);
1399            if issues.is_ok() {
1400                println!("{name} should fail (unknown procedure)");
1401                errors += 1;
1402            }
1403        }
1404
1405        {
1406            let name = "q39";
1407            let src = "BEGIN";
1408            let mut issues: Issues<'_> = Issues::new(src);
1409            let q = type_statement(&schema, src, &mut issues, &options);
1410            check_no_errors(name, src, issues.get(), &mut errors);
1411            if !matches!(q, StatementType::Transaction) {
1412                println!("{name} should be transaction");
1413                errors += 1;
1414            }
1415        }
1416
1417        {
1418            let name = "q40";
1419            let src = "COMMIT";
1420            let mut issues: Issues<'_> = Issues::new(src);
1421            let q = type_statement(&schema, src, &mut issues, &options);
1422            check_no_errors(name, src, issues.get(), &mut errors);
1423            if !matches!(q, StatementType::Transaction) {
1424                println!("{name} should be transaction");
1425                errors += 1;
1426            }
1427        }
1428
1429        {
1430            let name = "q41";
1431            let src = "SET @var = 42";
1432            let mut issues: Issues<'_> = Issues::new(src);
1433            let q = type_statement(&schema, src, &mut issues, &options);
1434            check_no_errors(name, src, issues.get(), &mut errors);
1435            if !matches!(q, StatementType::Set) {
1436                println!("{name} should be set");
1437                errors += 1;
1438            }
1439        }
1440
1441        {
1442            let name = "q42";
1443            let src = "SET @@session.time_zone = 'UTC'";
1444            let mut issues: Issues<'_> = Issues::new(src);
1445            let q = type_statement(&schema, src, &mut issues, &options);
1446            check_no_errors(name, src, issues.get(), &mut errors);
1447            if !matches!(q, StatementType::Set) {
1448                println!("{name} should be set");
1449                errors += 1;
1450            }
1451        }
1452
1453        {
1454            let name = "q42b";
1455            let src = "SET @@time_zone = '+00:00'";
1456            let mut issues: Issues<'_> = Issues::new(src);
1457            let q = type_statement(&schema, src, &mut issues, &options);
1458            check_no_errors(name, src, issues.get(), &mut errors);
1459            if !matches!(q, StatementType::Set) {
1460                println!("{name} should be set");
1461                errors += 1;
1462            }
1463        }
1464
1465        {
1466            let name = "q43";
1467            let src = "LOCK TABLES `t1` READ";
1468            let mut issues: Issues<'_> = Issues::new(src);
1469            let q = type_statement(&schema, src, &mut issues, &options);
1470            check_no_errors(name, src, issues.get(), &mut errors);
1471            if !matches!(q, StatementType::Lock) {
1472                println!("{name} should be lock");
1473                errors += 1;
1474            }
1475        }
1476
1477        {
1478            let name = "q44";
1479            let src = "LOCK TABLES `unknown_table` WRITE";
1480            let mut issues: Issues<'_> = Issues::new(src);
1481            type_statement(&schema, src, &mut issues, &options);
1482            if issues.is_ok() {
1483                println!("{name} should fail (unknown table)");
1484                errors += 1;
1485            }
1486        }
1487
1488        {
1489            let name = "q45";
1490            let src = "UNLOCK TABLES";
1491            let mut issues: Issues<'_> = Issues::new(src);
1492            let q = type_statement(&schema, src, &mut issues, &options);
1493            check_no_errors(name, src, issues.get(), &mut errors);
1494            if !matches!(q, StatementType::Lock) {
1495                println!("{name} should be lock");
1496                errors += 1;
1497            }
1498        }
1499
1500        if errors != 0 {
1501            panic!("{errors} errors in test");
1502        }
1503    }
1504
1505    #[test]
1506    fn postgresql() {
1507        let schema_src = "
1508        BEGIN;
1509
1510        DO $$ BEGIN
1511            CREATE TYPE my_enum AS ENUM (
1512            'V1',
1513            'V2',
1514            'V3'
1515        );
1516        EXCEPTION
1517            WHEN duplicate_object THEN null;
1518        END $$;
1519
1520        CREATE TABLE IF NOT EXISTS t1 (
1521            id bigint NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
1522            path text NOT NULL UNIQUE,
1523            v my_enum NOT NULL,
1524            time timestamptz NOT NULL DEFAULT now(),
1525            props jsonb NOT NULL DEFAULT '{}',
1526            old_id bigint,
1527            CONSTRAINT t1__old
1528            FOREIGN KEY(old_id) 
1529            REFERENCES t1(id)
1530            ON DELETE SET NULL
1531        );
1532
1533        CREATE TABLE IF NOT EXISTS t2 (
1534            id bigint NOT NULL PRIMARY KEY
1535        );
1536
1537        DROP INDEX IF EXISTS t2_index;
1538
1539        CREATE INDEX t2_index2 ON t2 (id);
1540
1541        CREATE TABLE IF NOT EXISTS t3 (
1542            a bigint NOT NULL,
1543            b bigint NOT NULL
1544        );
1545
1546        CREATE UNIQUE INDEX t3u ON t3(a,b);
1547
1548        COMMIT;
1549        ";
1550
1551        let options = TypeOptions::new().dialect(SQLDialect::PostgreSQL);
1552        let mut issues = Issues::new(schema_src);
1553        let schema = parse_schemas(schema_src, &mut issues, &options);
1554        let mut errors = 0;
1555        check_no_errors("schema", schema_src, issues.get(), &mut errors);
1556
1557        let options = TypeOptions::new()
1558            .dialect(SQLDialect::PostgreSQL)
1559            .arguments(SQLArguments::Dollar);
1560
1561        {
1562            let name = "q1";
1563            let src = "INSERT INTO t2 (id) SELECT id FROM t1 WHERE path=$1 ON CONFLICT (id) DO NOTHING RETURNING id";
1564            let mut 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            if let StatementType::Insert {
1568                arguments,
1569                returning,
1570                ..
1571            } = q
1572            {
1573                check_arguments(name, &arguments, "str", &mut errors);
1574                check_columns(
1575                    name,
1576                    src,
1577                    &returning.expect("Returning"),
1578                    "id:i64!",
1579                    &mut errors,
1580                );
1581            } else {
1582                println!("{name} should be select");
1583                errors += 1;
1584            }
1585        }
1586
1587        {
1588            let name = "q2";
1589            let src = "WITH hat AS (DELETE FROM t1 WHERE old_id=42 RETURNING id) INSERT INTO t2 (id) SELECT id FROM hat";
1590            let mut issues = Issues::new(src);
1591            let q = type_statement(&schema, src, &mut issues, &options);
1592            check_no_errors(name, src, issues.get(), &mut errors);
1593
1594            if let StatementType::Insert { arguments, .. } = q {
1595                check_arguments(name, &arguments, "", &mut errors);
1596            } else {
1597                println!("{name} should be select {q:?}");
1598                errors += 1;
1599            }
1600        }
1601
1602        {
1603            let name = "q3";
1604            let src = "INSERT INTO t1 (path) VALUES ('HI')";
1605            let mut issues: Issues<'_> = Issues::new(src);
1606            type_statement(&schema, src, &mut issues, &options);
1607            if issues.is_ok() {
1608                println!("{name} should fail");
1609                errors += 1;
1610            }
1611        }
1612
1613        {
1614            let name = "q4";
1615            let src = "INSERT INTO t1 (path, v) VALUES ('HI', 'V1')";
1616            let mut issues: Issues<'_> = Issues::new(src);
1617            let q = type_statement(&schema, src, &mut issues, &options);
1618            check_no_errors(name, src, issues.get(), &mut errors);
1619
1620            if let StatementType::Insert { arguments, .. } = q {
1621                check_arguments(name, &arguments, "", &mut errors);
1622            } else {
1623                println!("{name} should be insert {q:?}");
1624                errors += 1;
1625            }
1626        }
1627
1628        {
1629            let name = "q5";
1630            let src = "UPDATE t1 SET path='HI' RETURNING id";
1631            let mut issues: Issues<'_> = Issues::new(src);
1632            let q = type_statement(&schema, src, &mut issues, &options);
1633            if let StatementType::Update {
1634                arguments,
1635                returning,
1636                ..
1637            } = q
1638            {
1639                check_arguments(name, &arguments, "", &mut errors);
1640                if returning.is_none() {
1641                    println!("{name} should have returning");
1642                    errors += 1;
1643                }
1644            } else {
1645                println!("{name} should be update {q:?}");
1646                errors += 1;
1647            }
1648        }
1649
1650        {
1651            let name = "q6";
1652            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";
1653            let mut issues = Issues::new(src);
1654            let q = type_statement(&schema, src, &mut issues, &options);
1655            check_no_errors(name, src, issues.get(), &mut errors);
1656
1657            if let StatementType::Insert { arguments, .. } = q {
1658                check_arguments(name, &arguments, "", &mut errors);
1659            } else {
1660                println!("{name} should be insert {q:?}");
1661                errors += 1;
1662            }
1663        }
1664
1665        {
1666            let name = "q7";
1667            let src = "INSERT INTO t3 (a,b) VALUES (1, 1) ON CONFLICT (a,c) cake DO UPDATE SET a=2";
1668            let mut issues = Issues::new(src);
1669            type_statement(&schema, src, &mut issues, &options);
1670            if issues.is_ok() {
1671                println!("{name} should fail");
1672                errors += 1;
1673            }
1674        }
1675
1676        {
1677            let name = "q8";
1678            let src = "INSERT INTO t3 (a,b) VALUES (1, 1) ON CONFLICT (a,b) t3u DO UPDATE SET a=2 WHERE b=2";
1679            let mut issues = Issues::new(src);
1680            type_statement(&schema, src, &mut issues, &options);
1681            if issues.is_ok() {
1682                println!("{name} should fail");
1683                errors += 1;
1684            }
1685        }
1686
1687        {
1688            let name = "q9";
1689            let src = "SELECT left(path, -4) AS k FROM t1";
1690            let mut issues = Issues::new(src);
1691            let q = type_statement(&schema, src, &mut issues, &options);
1692
1693            check_no_errors(name, src, issues.get(), &mut errors);
1694            if let StatementType::Select { arguments, columns } = q {
1695                check_arguments(name, &arguments, "", &mut errors);
1696                check_columns(name, src, &columns, "k:str!", &mut errors);
1697            } else {
1698                println!("{name} should be select");
1699                errors += 1;
1700            }
1701        }
1702
1703        {
1704            let name = "json_extract";
1705            let src = "SELECT props -> 'a' AS k FROM t1";
1706            let mut issues = Issues::new(src);
1707            let q = type_statement(&schema, src, &mut issues, &options);
1708            check_no_errors(name, src, issues.get(), &mut errors);
1709            if let StatementType::Select { arguments, columns } = q {
1710                check_arguments(name, &arguments, "", &mut errors);
1711                // `->` returns a JSON value (jsonb in PostgreSQL), not text.
1712                // Nullable: a missing key yields SQL NULL.
1713                check_columns(name, src, &columns, "k:json", &mut errors);
1714            } else {
1715                println!("{name} should be select");
1716                errors += 1;
1717            }
1718        }
1719
1720        {
1721            let name = "json_extract_text";
1722            let src = "SELECT props ->> 'a' AS k FROM t1";
1723            let mut issues = Issues::new(src);
1724            let q = type_statement(&schema, src, &mut issues, &options);
1725            check_no_errors(name, src, issues.get(), &mut errors);
1726            if let StatementType::Select { arguments, columns } = q {
1727                check_arguments(name, &arguments, "", &mut errors);
1728                // `->>` returns the value as unquoted text.
1729                // Nullable: a missing key yields SQL NULL.
1730                check_columns(name, src, &columns, "k:str", &mut errors);
1731            } else {
1732                println!("{name} should be select");
1733                errors += 1;
1734            }
1735        }
1736
1737        {
1738            let name = "json_get_path";
1739            let src = "SELECT props #> '{a,b}' AS k FROM t1";
1740            let mut issues = Issues::new(src);
1741            let q = type_statement(&schema, src, &mut issues, &options);
1742            check_no_errors(name, src, issues.get(), &mut errors);
1743            if let StatementType::Select { arguments, columns } = q {
1744                check_arguments(name, &arguments, "", &mut errors);
1745                // `#>` returns a JSON value at a path, not text.
1746                // Nullable: a missing path yields SQL NULL.
1747                check_columns(name, src, &columns, "k:json", &mut errors);
1748            } else {
1749                println!("{name} should be select");
1750                errors += 1;
1751            }
1752        }
1753
1754        {
1755            let name = "json_get_path_text";
1756            let src = "SELECT props #>> '{a,b}' AS k FROM t1";
1757            let mut issues = Issues::new(src);
1758            let q = type_statement(&schema, src, &mut issues, &options);
1759            check_no_errors(name, src, issues.get(), &mut errors);
1760            if let StatementType::Select { arguments, columns } = q {
1761                check_arguments(name, &arguments, "", &mut errors);
1762                // `#>>` returns the value at a path as unquoted text.
1763                // Nullable: a missing path yields SQL NULL.
1764                check_columns(name, src, &columns, "k:str", &mut errors);
1765            } else {
1766                println!("{name} should be select");
1767                errors += 1;
1768            }
1769        }
1770
1771        {
1772            let name = "pg_set1";
1773            let src = "SET search_path = 'myschema'";
1774            let mut issues = Issues::new(src);
1775            let q = type_statement(&schema, src, &mut issues, &options);
1776            check_no_errors(name, src, issues.get(), &mut errors);
1777            if !matches!(q, StatementType::Set) {
1778                println!("{name} should be set");
1779                errors += 1;
1780            }
1781        }
1782
1783        {
1784            let name = "pg_set2";
1785            let src = "SET @user_var = 42";
1786            let mut issues = Issues::new(src);
1787            type_statement(&schema, src, &mut issues, &options);
1788            if issues.is_ok() {
1789                println!("{name} should fail: @var not valid in PostgreSQL");
1790                errors += 1;
1791            }
1792        }
1793
1794        {
1795            let name = "pg_set3";
1796            let src = "SET @@session.time_zone = 'UTC'";
1797            let mut issues = Issues::new(src);
1798            type_statement(&schema, src, &mut issues, &options);
1799            if issues.is_ok() {
1800                println!("{name} should fail: @@var not valid in PostgreSQL");
1801                errors += 1;
1802            }
1803        }
1804
1805        if errors != 0 {
1806            panic!("{errors} errors in test");
1807        }
1808    }
1809
1810    #[test]
1811    fn sqlite() {
1812        let schema_src = "
1813         CREATE TABLE IF NOT EXISTS `t1` (
1814            `id` INTEGER NOT NULL PRIMARY KEY,
1815            `sid` TEXT NOT NULL) STRICT;
1816        CREATE UNIQUE INDEX IF NOT EXISTS `t1_sid` ON `t1` (`sid`);
1817        ";
1818
1819        let options = TypeOptions::new().dialect(SQLDialect::Sqlite);
1820        let mut issues = Issues::new(schema_src);
1821        let schema = parse_schemas(schema_src, &mut issues, &options);
1822        let mut errors = 0;
1823        check_no_errors("schema", schema_src, issues.get(), &mut errors);
1824
1825        let options = TypeOptions::new()
1826            .dialect(SQLDialect::Sqlite)
1827            .arguments(SQLArguments::QuestionMark);
1828
1829        {
1830            let name = "q1";
1831            let src = "INSERT INTO `t1` (`sid`) VALUES (?)";
1832            let mut issues = Issues::new(src);
1833            let q = type_statement(&schema, src, &mut issues, &options);
1834            check_no_errors(name, src, issues.get(), &mut errors);
1835            if !matches!(q, StatementType::Insert { .. }) {
1836                println!("{name} should be select");
1837                errors += 1;
1838            }
1839        }
1840
1841        if errors != 0 {
1842            panic!("{errors} errors in test");
1843        }
1844    }
1845
1846    /// CREATE SCHEMA / qualified table names work in PostgreSQL.
1847    #[test]
1848    fn postgres_schema_support() {
1849        let schema_src = "
1850        CREATE SCHEMA myschema;
1851
1852        CREATE TABLE myschema.items (
1853            id   bigint NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
1854            name text   NOT NULL
1855        );
1856
1857        -- Unqualified name lands in 'public' schema
1858        CREATE TABLE products (
1859            id    bigint NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
1860            label text   NOT NULL
1861        );
1862        ";
1863
1864        let opts = TypeOptions::new()
1865            .dialect(SQLDialect::PostgreSQL)
1866            .arguments(SQLArguments::Dollar);
1867        let mut issues = Issues::new(schema_src);
1868        let schema = parse_schemas(schema_src, &mut issues, &opts);
1869        let mut errors = 0;
1870        check_no_errors("schema", schema_src, issues.get(), &mut errors);
1871
1872        // Qualified SELECT against myschema.items
1873        {
1874            let src = "SELECT id, name FROM myschema.items WHERE id = $1";
1875            let mut issues = Issues::new(src);
1876            let q = type_statement(&schema, src, &mut issues, &opts);
1877            check_no_errors("qualified SELECT", src, issues.get(), &mut errors);
1878            if let StatementType::Select { columns, arguments } = q {
1879                assert_eq!(columns.len(), 2, "expected 2 columns");
1880                assert_eq!(arguments.len(), 1, "expected 1 argument");
1881            } else {
1882                errors += 1;
1883                println!("qualified SELECT: expected Select");
1884            }
1885        }
1886
1887        // Unqualified SELECT against public.products
1888        {
1889            let src = "SELECT id, label FROM products WHERE id = $1";
1890            let mut issues = Issues::new(src);
1891            let q = type_statement(&schema, src, &mut issues, &opts);
1892            check_no_errors("unqualified SELECT", src, issues.get(), &mut errors);
1893            if let StatementType::Select { columns, .. } = q {
1894                assert_eq!(columns.len(), 2, "expected 2 columns");
1895            } else {
1896                errors += 1;
1897                println!("unqualified SELECT: expected Select");
1898            }
1899        }
1900
1901        // INSERT into qualified table
1902        {
1903            let src = "INSERT INTO myschema.items (name) VALUES ($1)";
1904            let mut issues = Issues::new(src);
1905            let q = type_statement(&schema, src, &mut issues, &opts);
1906            check_no_errors("qualified INSERT", src, issues.get(), &mut errors);
1907            assert!(matches!(q, StatementType::Insert { .. }), "expected Insert");
1908        }
1909
1910        // DROP SCHEMA removes all tables in that schema
1911        {
1912            let schema_src2 = "
1913            CREATE SCHEMA tmp;
1914            CREATE TABLE tmp.t (id bigint NOT NULL);
1915            DROP SCHEMA tmp;
1916            ";
1917            let mut issues2 = Issues::new(schema_src2);
1918            let s2 = parse_schemas(schema_src2, &mut issues2, &opts);
1919            check_no_errors("drop schema", schema_src2, issues2.get(), &mut errors);
1920            // tmp.t should be gone
1921            let src = "SELECT id FROM tmp.t";
1922            let mut issues2 = Issues::new(src);
1923            let q = type_statement(&s2, src, &mut issues2, &opts);
1924            assert!(
1925                !issues2.is_ok(),
1926                "expected error: tmp.t should not exist after DROP SCHEMA"
1927            );
1928            let _ = q;
1929        }
1930
1931        if errors != 0 {
1932            panic!("{errors} errors in postgres_schema_support test");
1933        }
1934    }
1935
1936    /// CREATE SCHEMA must fail in MySQL; schema-qualified names must also fail.
1937    #[test]
1938    fn mysql_create_schema_fails() {
1939        let opts_maria = TypeOptions::new()
1940            .dialect(SQLDialect::MariaDB)
1941            .arguments(SQLArguments::QuestionMark);
1942
1943        // CREATE SCHEMA is not supported in MySQL
1944        {
1945            let src = "CREATE SCHEMA myschema;";
1946            let mut issues = Issues::new(src);
1947            let _ = parse_schemas(src, &mut issues, &opts_maria);
1948            assert!(
1949                !issues.is_ok(),
1950                "CREATE SCHEMA should produce an error in MySQL"
1951            );
1952        }
1953
1954        // Schema-qualified table name in CREATE TABLE should fail in MySQL
1955        {
1956            let src = "CREATE TABLE myschema.items (id int NOT NULL);";
1957            let mut issues = Issues::new(src);
1958            let _ = parse_schemas(src, &mut issues, &opts_maria);
1959            assert!(
1960                !issues.is_ok(),
1961                "schema.table in CREATE TABLE should fail in MySQL"
1962            );
1963        }
1964
1965        // Schema-qualified table name in SELECT should fail in MySQL
1966        {
1967            let tbl_src = "CREATE TABLE items (id int NOT NULL);";
1968            let mut issues = Issues::new(tbl_src);
1969            let schema = parse_schemas(tbl_src, &mut issues, &opts_maria);
1970            assert!(issues.is_ok());
1971
1972            let src = "SELECT id FROM myschema.items";
1973            let mut issues = Issues::new(src);
1974            let _ = type_statement(&schema, src, &mut issues, &opts_maria);
1975            assert!(!issues.is_ok(), "schema.table in FROM should fail in MySQL");
1976        }
1977    }
1978
1979    /// PostgreSQL range and multirange types: parsing, operators and functions.
1980    /// See https://www.postgresql.org/docs/15/rangetypes.html and
1981    /// https://www.postgresql.org/docs/15/functions-range.html
1982    #[test]
1983    fn postgresql_range_types() {
1984        let schema_src = "
1985        CREATE TABLE reservation (
1986            id bigint NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
1987            during tsrange NOT NULL,
1988            ids int4multirange NOT NULL
1989        );
1990        ";
1991
1992        let opts = TypeOptions::new()
1993            .dialect(SQLDialect::PostgreSQL)
1994            .arguments(SQLArguments::Dollar);
1995        let mut issues = Issues::new(schema_src);
1996        let schema = parse_schemas(schema_src, &mut issues, &opts);
1997        let mut errors = 0;
1998        check_no_errors("schema", schema_src, issues.get(), &mut errors);
1999
2000        // Checks that `src` types as a single-column SELECT with the given type, or,
2001        // when `expected` is `None`, that typing `src` produces a type error.
2002        let mut check_expr = |src: &'static str, expected: Option<Type<'static>>| {
2003            let mut issues = Issues::new(src);
2004            let q = type_statement(&schema, src, &mut issues, &opts);
2005            let Some(expected) = expected else {
2006                if issues.is_ok() {
2007                    println!("{src}: expected a type error");
2008                    errors += 1;
2009                }
2010                return;
2011            };
2012            check_no_errors(src, src, issues.get(), &mut errors);
2013            if let StatementType::Select { columns, .. } = q {
2014                match columns.first() {
2015                    Some(c) if c.type_.t == expected => {}
2016                    Some(c) => {
2017                        println!("{src}: expected type {expected} got {}", c.type_.t);
2018                        errors += 1;
2019                    }
2020                    None => {
2021                        println!("{src}: expected a column, got none");
2022                        errors += 1;
2023                    }
2024                }
2025            } else {
2026                println!("{src}: expected Select");
2027                errors += 1;
2028            }
2029        };
2030        // Range constructors (8.17.6)
2031        check_expr(
2032            "SELECT int4range(1, 10)",
2033            Some(Type::Range(Box::new(Type::I32))),
2034        );
2035        check_expr(
2036            "SELECT int4range(1, 10, '[]')",
2037            Some(Type::Range(Box::new(Type::I32))),
2038        );
2039        check_expr(
2040            "SELECT int8range(1, 10)",
2041            Some(Type::Range(Box::new(Type::I64))),
2042        );
2043        check_expr(
2044            "SELECT numrange(1.1, 2.2)",
2045            Some(Type::Range(Box::new(BaseType::Float.into()))),
2046        );
2047        check_expr(
2048            "SELECT daterange('2010-01-01'::date, '2010-01-02'::date)",
2049            Some(Type::Range(Box::new(BaseType::Date.into()))),
2050        );
2051        check_expr(
2052            "SELECT tstzrange(now(), now())",
2053            Some(Type::Range(Box::new(BaseType::TimeStamp.into()))),
2054        );
2055
2056        // Multirange constructors
2057        check_expr(
2058            "SELECT int4multirange(int4range(1, 2), int4range(3, 4))",
2059            Some(Type::MultiRange(Box::new(Type::I32))),
2060        );
2061        check_expr(
2062            "SELECT int4multirange()",
2063            Some(Type::MultiRange(Box::new(Type::I32))),
2064        );
2065        check_expr(
2066            "SELECT multirange(int4range(1, 2))",
2067            Some(Type::MultiRange(Box::new(Type::I32))),
2068        );
2069
2070        // Range/multirange operators (Table 9.54, 9.55)
2071        check_expr(
2072            "SELECT int4range(10, 20) @> 3",
2073            Some(Type::Base(BaseType::Bool)),
2074        );
2075        check_expr(
2076            "SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0)",
2077            Some(Type::Base(BaseType::Bool)),
2078        );
2079        check_expr(
2080            "SELECT int8range(1, 10) << int8range(100, 110)",
2081            Some(Type::Base(BaseType::Bool)),
2082        );
2083        check_expr(
2084            "SELECT int8range(50, 60) >> int8range(20, 30)",
2085            Some(Type::Base(BaseType::Bool)),
2086        );
2087        check_expr(
2088            "SELECT int8range(1, 20) &< int8range(18, 20)",
2089            Some(Type::Base(BaseType::Bool)),
2090        );
2091        check_expr(
2092            "SELECT int8range(7, 20) &> int8range(5, 10)",
2093            Some(Type::Base(BaseType::Bool)),
2094        );
2095        check_expr(
2096            "SELECT numrange(1.1, 2.2) -|- numrange(2.2, 3.3)",
2097            Some(Type::Base(BaseType::Bool)),
2098        );
2099        check_expr(
2100            "SELECT numrange(5.0, 15.0) + numrange(10.0, 20.0)",
2101            Some(Type::Range(Box::new(BaseType::Float.into()))),
2102        );
2103        check_expr(
2104            "SELECT int8range(5, 15) * int8range(10, 20)",
2105            Some(Type::Range(Box::new(Type::I64))),
2106        );
2107        check_expr(
2108            "SELECT int8range(5, 15) - int8range(10, 20)",
2109            Some(Type::Range(Box::new(Type::I64))),
2110        );
2111        // Plain integer bit-shift/bitwise ops must still work as before.
2112        check_expr("SELECT 1 << 2", Some(Type::Base(BaseType::Integer)));
2113        check_expr("SELECT 1 & 2", Some(Type::Base(BaseType::Integer)));
2114
2115        // Range/multirange functions (Table 9.56, 9.57)
2116        check_expr("SELECT upper(int8range(15, 25))", Some(Type::I64));
2117        check_expr(
2118            "SELECT lower(numrange(1.1, 2.2))",
2119            Some(Type::Base(BaseType::Float)),
2120        );
2121        check_expr(
2122            "SELECT isempty(numrange(1.0, 5.0))",
2123            Some(Type::Base(BaseType::Bool)),
2124        );
2125        check_expr(
2126            "SELECT lower_inc(numrange(1.1, 2.2))",
2127            Some(Type::Base(BaseType::Bool)),
2128        );
2129        check_expr(
2130            "SELECT upper_inc(numrange(1.1, 2.2))",
2131            Some(Type::Base(BaseType::Bool)),
2132        );
2133        check_expr(
2134            "SELECT lower_inf('(,)'::daterange)",
2135            Some(Type::Base(BaseType::Bool)),
2136        );
2137        check_expr(
2138            "SELECT upper_inf('(,)'::daterange)",
2139            Some(Type::Base(BaseType::Bool)),
2140        );
2141        check_expr(
2142            "SELECT range_merge(int4range(1, 2), int4range(3, 4))",
2143            Some(Type::Range(Box::new(Type::I32))),
2144        );
2145        check_expr(
2146            "SELECT range_merge('{[1,2), [3,4)}'::int4multirange)",
2147            Some(Type::Range(Box::new(Type::I32))),
2148        );
2149        check_expr(
2150            "SELECT unnest('{[1,2), [3,4)}'::int4multirange)",
2151            Some(Type::Range(Box::new(Type::I32))),
2152        );
2153
2154        // Columns typed from the schema, including lower()/upper() on a `tsrange` column
2155        // and the `int4multirange` -> `int4range` unnest() table-function overload.
2156        check_expr(
2157            "SELECT lower(during) FROM reservation",
2158            Some(Type::Base(BaseType::DateTime)),
2159        );
2160        check_expr(
2161            "SELECT r FROM unnest('{[1,2), [3,4)}'::int4multirange) AS u(r)",
2162            Some(Type::Range(Box::new(Type::I32))),
2163        );
2164
2165        // Mismatched element types / mismatched range vs. multirange must be type errors.
2166        check_expr("SELECT int4range(1, 2) + numrange(1.0, 2.0)", None);
2167        check_expr("SELECT int4range(1, 2) + '{[1,2)}'::int4multirange", None);
2168
2169        if errors != 0 {
2170            panic!("{errors} errors in postgresql_range_types test");
2171        }
2172    }
2173
2174    /// A row-constructor `IN` a subquery that yields matching columns via
2175    /// `UNNEST($1::bigint[], $2::bigint[])`. Reported as a real-world usage that
2176    /// should type-check.
2177    #[test]
2178    fn postgresql_row_in_unnest_subquery() {
2179        let schema_src = "
2180        CREATE TABLE t1 (
2181            id bigint NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
2182            a bigint NOT NULL,
2183            b bigint NOT NULL
2184        );
2185        ";
2186
2187        let opts = TypeOptions::new()
2188            .dialect(SQLDialect::PostgreSQL)
2189            .arguments(SQLArguments::Dollar);
2190        let mut issues = Issues::new(schema_src);
2191        let schema = parse_schemas(schema_src, &mut issues, &opts);
2192        let mut errors = 0;
2193        check_no_errors("schema", schema_src, issues.get(), &mut errors);
2194
2195        let src = "SELECT id, a, b FROM t1
2196                WHERE (a, b) IN (
2197                    SELECT * FROM UNNEST($1::bigint[], $2::bigint[])
2198                )";
2199        let mut issues = Issues::new(src);
2200        let q = type_statement(&schema, src, &mut issues, &opts);
2201        check_no_errors("query", src, issues.get(), &mut errors);
2202        if let StatementType::Select { columns, arguments } = q {
2203            if columns.len() != 3 {
2204                println!("query: expected 3 columns, got {}", columns.len());
2205                errors += 1;
2206            }
2207            if arguments.len() != 2 {
2208                println!("query: expected 2 arguments, got {}", arguments.len());
2209                errors += 1;
2210            }
2211        } else {
2212            println!("query: expected Select");
2213            errors += 1;
2214        }
2215
2216        if errors != 0 {
2217            panic!("{errors} errors in postgresql_row_in_unnest_subquery test");
2218        }
2219    }
2220
2221    /// `col = ANY($1)` with a plain, uncast argument (no `$1::bigint[]`) should infer the
2222    /// argument as an array matching the column type. Reported as a real-world usage that
2223    /// should type-check with exactly one (array-typed) argument.
2224    #[test]
2225    fn postgresql_eq_any_uncast_arg() {
2226        let schema_src = "
2227        CREATE TABLE t1 (
2228            a_id bigint NOT NULL,
2229            b_id bigint NOT NULL
2230        );
2231        CREATE TABLE t2 (
2232            id bigint NOT NULL PRIMARY KEY,
2233            x bigint NOT NULL
2234        );
2235        ";
2236
2237        let opts = TypeOptions::new()
2238            .dialect(SQLDialect::PostgreSQL)
2239            .arguments(SQLArguments::Dollar);
2240        let mut issues = Issues::new(schema_src);
2241        let schema = parse_schemas(schema_src, &mut issues, &opts);
2242        let mut errors = 0;
2243        check_no_errors("schema", schema_src, issues.get(), &mut errors);
2244
2245        let src = "SELECT a_id, b_id, x FROM t1, t2 WHERE a_id = ANY($1) AND b_id = t2.id";
2246        let mut issues = Issues::new(src);
2247        let q = type_statement(&schema, src, &mut issues, &opts);
2248        check_no_errors("query", src, issues.get(), &mut errors);
2249        if let StatementType::Select { columns, arguments } = q {
2250            if columns.len() != 3 {
2251                println!("query: expected 3 columns, got {}", columns.len());
2252                errors += 1;
2253            }
2254            match arguments.first() {
2255                Some((_, t)) if t.t == Type::Array(Box::new(BaseType::Integer.into())) => {}
2256                Some((_, t)) => {
2257                    println!("query: expected argument 0 of type integer[] got {}", t.t);
2258                    errors += 1;
2259                }
2260                None => {
2261                    println!("query: expected an argument, got none");
2262                    errors += 1;
2263                }
2264            }
2265            if arguments.len() != 1 {
2266                println!("query: expected 1 argument, got {}", arguments.len());
2267                errors += 1;
2268            }
2269        } else {
2270            println!("query: expected Select");
2271            errors += 1;
2272        }
2273
2274        if errors != 0 {
2275            panic!("{errors} errors in postgresql_eq_any_uncast_arg test");
2276        }
2277    }
2278
2279    /// `INSERT ... SELECT * FROM UNNEST(...)` where one of the unnested arrays is `bytea[]`.
2280    /// Reported as a real-world usage that should type-check.
2281    #[test]
2282    fn postgresql_insert_unnest_bytea_array() {
2283        let schema_src = "
2284        CREATE TABLE t1 (
2285            a bigint NOT NULL,
2286            b bigint NOT NULL,
2287            payload bytea NOT NULL,
2288            PRIMARY KEY (a, b)
2289        );
2290        ";
2291
2292        let opts = TypeOptions::new()
2293            .dialect(SQLDialect::PostgreSQL)
2294            .arguments(SQLArguments::Dollar);
2295        let mut issues = Issues::new(schema_src);
2296        let schema = parse_schemas(schema_src, &mut issues, &opts);
2297        let mut errors = 0;
2298        check_no_errors("schema", schema_src, issues.get(), &mut errors);
2299
2300        let src = "INSERT INTO t1 (a, b, payload)
2301                    SELECT * FROM UNNEST($1::bigint[], $2::bigint[], $3::bytea[])
2302                    ON CONFLICT DO NOTHING
2303                    RETURNING a, b";
2304        let mut issues = Issues::new(src);
2305        let q = type_statement(&schema, src, &mut issues, &opts);
2306        check_no_errors("query", src, issues.get(), &mut errors);
2307        if let StatementType::Insert {
2308            arguments,
2309            returning,
2310            ..
2311        } = q
2312        {
2313            match returning {
2314                Some(columns) if columns.len() == 2 => {}
2315                Some(columns) => {
2316                    println!("query: expected 2 returning columns, got {}", columns.len());
2317                    errors += 1;
2318                }
2319                None => {
2320                    println!("query: expected returning columns, got none");
2321                    errors += 1;
2322                }
2323            }
2324            if arguments.len() != 3 {
2325                println!("query: expected 3 arguments, got {}", arguments.len());
2326                errors += 1;
2327            }
2328        } else {
2329            println!("query: expected Insert");
2330            errors += 1;
2331        }
2332
2333        if errors != 0 {
2334            panic!("{errors} errors in postgresql_insert_unnest_bytea_array test");
2335        }
2336    }
2337
2338    /// A writable CTE that unions a range into an `int8multirange` column and then
2339    /// `unnest()`s the result. Reported as a real-world usage that should type-check.
2340    #[test]
2341    fn postgresql_multirange_update_returning_unnest() {
2342        let schema_src = "
2343        CREATE TABLE partial_files (
2344            id uuid NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
2345            uploaded_bytes int8multirange NOT NULL DEFAULT '{}',
2346            last_modified timestamptz NOT NULL DEFAULT now()
2347        );
2348        ";
2349
2350        let opts = TypeOptions::new()
2351            .dialect(SQLDialect::PostgreSQL)
2352            .arguments(SQLArguments::Dollar);
2353        let mut issues = Issues::new(schema_src);
2354        let schema = parse_schemas(schema_src, &mut issues, &opts);
2355        let mut errors = 0;
2356        check_no_errors("schema", schema_src, issues.get(), &mut errors);
2357
2358        let src = "
2359        WITH the_update AS (
2360          UPDATE partial_files
2361          SET last_modified = now(),
2362            uploaded_bytes = uploaded_bytes + multirange(int8range($2, $3, '[)'))
2363          WHERE id = $1
2364          RETURNING uploaded_bytes
2365        )
2366        SELECT unnest(uploaded_bytes) AS \"range\"
2367        FROM the_update
2368        ";
2369        let mut issues = Issues::new(src);
2370        let q = type_statement(&schema, src, &mut issues, &opts);
2371        check_no_errors("query", src, issues.get(), &mut errors);
2372        if let StatementType::Select { columns, arguments } = q {
2373            match columns.first() {
2374                Some(c) if c.type_.t == Type::Range(Box::new(Type::I64)) => {}
2375                Some(c) => {
2376                    println!(
2377                        "query: expected column 0 of type range(i64) got {}",
2378                        c.type_.t
2379                    );
2380                    errors += 1;
2381                }
2382                None => {
2383                    println!("query: expected a column, got none");
2384                    errors += 1;
2385                }
2386            }
2387            if arguments.len() != 3 {
2388                println!("query: expected 3 arguments, got {}", arguments.len());
2389                errors += 1;
2390            }
2391        } else {
2392            println!("query: expected Select");
2393            errors += 1;
2394        }
2395
2396        if errors != 0 {
2397            panic!("{errors} errors in postgresql_multirange_update_returning_unnest test");
2398        }
2399    }
2400
2401    #[test]
2402    fn postgresql_alter_sequence() {
2403        let schema_src = "
2404        CREATE SEQUENCE tsng_datasets2_id_seq;
2405        ALTER SEQUENCE tsng_datasets2_id_seq OWNER TO compute;
2406        ALTER SEQUENCE tsng_datasets2_id_seq RENAME TO datasets_id_seq;
2407        ALTER SEQUENCE IF EXISTS datasets_id_seq
2408            INCREMENT BY 2
2409            MINVALUE 1
2410            MAXVALUE 1000
2411            START WITH 1
2412            RESTART WITH 42
2413            CACHE 10
2414            CYCLE
2415            OWNED BY none;
2416        ";
2417
2418        let opts = TypeOptions::new()
2419            .dialect(SQLDialect::PostgreSQL)
2420            .arguments(SQLArguments::Dollar);
2421        let mut issues = Issues::new(schema_src);
2422        let schemas = parse_schemas(schema_src, &mut issues, &opts);
2423        let mut errors = 0;
2424        check_no_errors("schema", schema_src, issues.get(), &mut errors);
2425
2426        // The sequence was renamed: the registry must contain the new name and no
2427        // longer the old one.
2428        use crate::schema::{QualifiedIdentifier, lookup_name};
2429        let search_path: &[&str] = &["public"];
2430        let new_key = QualifiedIdentifier::Qualified(
2431            Identifier::new("public", 0..0),
2432            Identifier::new("datasets_id_seq", 0..0),
2433        );
2434        if lookup_name(&schemas.sequences, &new_key, search_path).is_none() {
2435            println!("rename: datasets_id_seq missing from sequence registry");
2436            errors += 1;
2437        }
2438        let old_key = QualifiedIdentifier::Qualified(
2439            Identifier::new("public", 0..0),
2440            Identifier::new("tsng_datasets2_id_seq", 0..0),
2441        );
2442        if lookup_name(&schemas.sequences, &old_key, search_path).is_some() {
2443            println!("rename: old sequence name still in registry");
2444            errors += 1;
2445        }
2446
2447        // Altering an unknown sequence is an error (without IF EXISTS)
2448        {
2449            let src = "ALTER SEQUENCE unknown_seq OWNER TO compute;";
2450            let mut issues = Issues::new(src);
2451            let _schemas = parse_schemas(src, &mut issues, &opts);
2452            if issues.get().is_empty() {
2453                println!("unknown seq: expected an error, got none");
2454                errors += 1;
2455            }
2456        }
2457        // ... but fine with IF EXISTS
2458        {
2459            let src = "ALTER SEQUENCE IF EXISTS unknown_seq OWNER TO compute;";
2460            let mut issues = Issues::new(src);
2461            let _schemas = parse_schemas(src, &mut issues, &opts);
2462            check_no_errors("if exists", src, issues.get(), &mut errors);
2463        }
2464        // Bare RESTART (with or without value) is valid in CREATE and ALTER
2465        {
2466            let src = "
2467            CREATE SEQUENCE s1 RESTART;
2468            CREATE SEQUENCE s2 RESTART WITH 42;
2469            CREATE SEQUENCE s3 RESTART 42;
2470            ALTER SEQUENCE s1 RESTART;
2471            ALTER SEQUENCE s2 RESTART WITH 42;
2472            ALTER SEQUENCE s3 RESTART 42;
2473            ";
2474            let mut issues = Issues::new(src);
2475            let _schemas = parse_schemas(src, &mut issues, &opts);
2476            check_no_errors("restart forms", src, issues.get(), &mut errors);
2477        }
2478        // RESTART WITH requires a value: bare RESTART alone may omit it
2479        for src in [
2480            "ALTER SEQUENCE s RESTART WITH;",
2481            "CREATE SEQUENCE s RESTART WITH;",
2482        ] {
2483            let mut issues = Issues::new(src);
2484            let _schemas = parse_schemas(src, &mut issues, &opts);
2485            if issues.get().is_empty() {
2486                println!("restart with: expected an error for '{src}', got none");
2487                errors += 1;
2488            }
2489        }
2490        // ALTER SEQUENCE with a bare name must resolve through the search path and
2491        // rename the canonical (schema-qualified) entry.
2492        {
2493            let src = "
2494            CREATE SCHEMA app;
2495            CREATE SEQUENCE app.s;
2496            SET search_path TO app;
2497            ALTER SEQUENCE s RENAME TO t;
2498            ";
2499            let mut issues = Issues::new(src);
2500            let schemas = parse_schemas(src, &mut issues, &opts);
2501            check_no_errors("search path rename", src, issues.get(), &mut errors);
2502            let in_app = |name: &str| {
2503                lookup_name(
2504                    &schemas.sequences,
2505                    &QualifiedIdentifier::Unqualified(Identifier::new(name, 0..0)),
2506                    ["app"].as_slice(),
2507                )
2508                .is_some()
2509            };
2510            if !in_app("t") {
2511                println!("search path rename: app.t missing from sequence registry");
2512                errors += 1;
2513            }
2514            if in_app("s") {
2515                println!("search path rename: app.s still in sequence registry");
2516                errors += 1;
2517            }
2518        }
2519        // A bare name that is not on the search path must not resolve to a sequence
2520        // in another schema.
2521        {
2522            let src = "
2523            CREATE SCHEMA app;
2524            CREATE SEQUENCE app.s;
2525            ALTER SEQUENCE s RENAME TO t;
2526            ";
2527            let mut issues = Issues::new(src);
2528            let _schemas = parse_schemas(src, &mut issues, &opts);
2529            if issues.get().is_empty() {
2530                println!("search path miss: expected an error, got none");
2531                errors += 1;
2532            }
2533        }
2534
2535        if errors != 0 {
2536            panic!("{errors} errors in postgresql_alter_sequence test");
2537        }
2538    }
2539}