diff --git a/Cargo.lock b/Cargo.lock index 545aa6095..a1839a233 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3949,6 +3949,21 @@ dependencies = [ "turso_core", ] +[[package]] +name = "turso_parser" +version = "0.1.4-pre.8" +dependencies = [ + "bitflags 2.9.0", + "criterion", + "fallible-iterator", + "miette", + "pprof", + "serde", + "strum", + "strum_macros", + "turso_sqlite3_parser", +] + [[package]] name = "turso_sqlite3" version = "0.1.4-pre.8" diff --git a/Cargo.toml b/Cargo.toml index 27de21391..f1ed301f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "testing/sqlite_test_ext", "tests", "vendored/sqlite3-parser/sqlparser_bench", + "parser", "packages/turso-sync-engine", "packages/turso-sync-js", ] diff --git a/parser/Cargo.toml b/parser/Cargo.toml new file mode 100644 index 000000000..0bd35520c --- /dev/null +++ b/parser/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "turso_parser" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "The Turso parser library" + +[lib] +name = "turso_parser" + +[features] +default = [] +serde = ["dep:serde", "bitflags/serde"] + +[dependencies] +bitflags = "2.0" +miette = "7.4.0" +strum = { workspace = true } +strum_macros = {workspace = true } +serde = { workspace = true , optional = true, features = ["derive"] } + +[dev-dependencies] +fallible-iterator = "0.3" +criterion = { version = "0.5", features = ["html_reports" ] } +turso_sqlite3_parser = { workspace = true } + +[target.'cfg(not(target_family = "windows"))'.dev-dependencies] +pprof = { version = "0.14.0", features = ["criterion", "flamegraph"] } + +[[bench]] +name = "parser_benchmark" +harness = false diff --git a/parser/README.md b/parser/README.md new file mode 100644 index 000000000..1333ed77b --- /dev/null +++ b/parser/README.md @@ -0,0 +1 @@ +TODO diff --git a/parser/benches/parser_benchmark.rs b/parser/benches/parser_benchmark.rs new file mode 100644 index 000000000..0c6291553 --- /dev/null +++ b/parser/benches/parser_benchmark.rs @@ -0,0 +1,102 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use fallible_iterator::FallibleIterator; +use pprof::criterion::{Output, PProfProfiler}; +use turso_parser::{lexer::Lexer, parser::Parser}; +use turso_sqlite3_parser::lexer::{ + sql::{Parser as OldParser, Tokenizer}, + Scanner, +}; + +fn bench_parser(criterion: &mut Criterion) { + let queries = [ + "SELECT 1", + "SELECT * FROM users LIMIT 1", + "SELECT first_name, count(1) FROM users GROUP BY first_name HAVING count(1) > 1 ORDER BY count(1) LIMIT 1", + ]; + + for query in queries.iter() { + let mut group = criterion.benchmark_group(format!("Parser `{query}`")); + let qb = query.as_bytes(); + + group.bench_function(BenchmarkId::new("limbo_parser_query", ""), |b| { + b.iter(|| Parser::new(black_box(qb)).next().unwrap()); + }); + + group.bench_function(BenchmarkId::new("limbo_old_parser_query", ""), |b| { + b.iter(|| { + OldParser::new(black_box(qb)).next().unwrap().unwrap(); + }); + }); + + group.finish(); + } +} + +fn bench_parser_insert_batch(criterion: &mut Criterion) { + for batch_size in [1, 10, 100] { + let mut values = String::from("INSERT INTO test VALUES "); + for i in 0..batch_size { + if i > 0 { + values.push(','); + } + values.push_str(&format!("({}, '{}')", i, format_args!("value_{i}"))); + } + + let mut group = criterion.benchmark_group(format!("Parser insert batch `{values}`")); + let qb = values.as_bytes(); + + group.bench_function(BenchmarkId::new("limbo_parser_insert_batch", ""), |b| { + b.iter(|| Parser::new(black_box(qb)).next().unwrap()); + }); + + group.bench_function(BenchmarkId::new("limbo_old_parser_insert_batch", ""), |b| { + b.iter(|| { + OldParser::new(black_box(qb)).next().unwrap().unwrap(); + }); + }); + + group.finish(); + } +} + +fn bench_lexer(criterion: &mut Criterion) { + let queries = [ + "SELECT 1", + "SELECT * FROM users LIMIT 1", + "SELECT first_name, count(1) FROM users GROUP BY first_name HAVING count(1) > 1 ORDER BY count(1) LIMIT 1", + ]; + + for query in queries.iter() { + let mut group = criterion.benchmark_group(format!("Lexer `{query}`")); + let qb = query.as_bytes(); + + group.bench_function(BenchmarkId::new("limbo_lexer_query", ""), |b| { + b.iter(|| { + for token in Lexer::new(black_box(qb)) { + token.unwrap(); + } + }); + }); + + group.bench_function(BenchmarkId::new("limbo_old_lexer_query", ""), |b| { + b.iter(|| { + let tokenizer = Tokenizer::new(); + let mut scanner = Scanner::new(black_box(tokenizer)); + loop { + if let (_, None, _) = scanner.scan(black_box(qb)).unwrap() { + break; + } + } + }); + }); + + group.finish(); + } +} + +criterion_group! { + name = benches; + config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); + targets = bench_parser, bench_parser_insert_batch, bench_lexer +} +criterion_main!(benches); diff --git a/parser/build.rs b/parser/build.rs new file mode 100644 index 000000000..a6b8db946 --- /dev/null +++ b/parser/build.rs @@ -0,0 +1,290 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::PathBuf; + +/// generates a trie-like function with nested match expressions for parsing SQL keywords +/// example: input: [["ABORT", "TK_ABORT"], ["ACTION", "TK_ACTION"], ["ADD", "TK_ADD"],] +/// A +/// ├─ B +/// │ ├─ O +/// │ │ ├─ R +/// │ │ │ ├─ T -> TK_ABORT +/// ├─ C +/// │ ├─ T +/// │ │ ├─ I +/// │ │ │ ├─ O +/// │ │ │ │ ├─ N -> TK_ACTION +/// ├─ D +/// │ ├─ D -> TK_ADD +fn build_keyword_map( + writer: &mut impl Write, + func_name: &str, + keywords: &[[&'static str; 2]], +) -> Result<(), std::io::Error> { + assert!(!keywords.is_empty()); + let mut min_len = keywords[0][0].len(); + let mut max_len = keywords[0][0].len(); + + struct PathEntry { + result: Option<&'static str>, + sub_entries: HashMap>, + } + + let mut paths = Box::new(PathEntry { + result: None, + sub_entries: HashMap::new(), + }); + + for keyword in keywords { + let keyword_b = keyword[0].as_bytes(); + + if keyword_b.len() < min_len { + min_len = keyword_b.len(); + } + + if keyword_b.len() > max_len { + max_len = keyword_b.len(); + } + + let mut current = &mut paths; + + for &b in keyword_b { + let upper_b = b.to_ascii_uppercase(); + + match current.sub_entries.get(&upper_b) { + Some(_) => { + current = current.sub_entries.get_mut(&upper_b).unwrap(); + } + None => { + let new_entry = Box::new(PathEntry { + result: None, + sub_entries: HashMap::new(), + }); + current.sub_entries.insert(upper_b, new_entry); + current = current.sub_entries.get_mut(&upper_b).unwrap(); + } + } + } + + assert!(current.result.is_none()); + current.result = Some(keyword[1]); + } + + fn write_entry(writer: &mut impl Write, entry: &PathEntry) -> Result<(), std::io::Error> { + if let Some(result) = entry.result { + writeln!(writer, "if idx == buf.len() {{")?; + writeln!(writer, "return Some(TokenType::{result});")?; + writeln!(writer, "}}")?; + } + + if entry.sub_entries.is_empty() { + writeln!(writer, "None")?; + return Ok(()); + } + + writeln!(writer, "if idx >= buf.len() {{")?; + writeln!(writer, "return None;")?; + writeln!(writer, "}}")?; + + writeln!(writer, "match buf[idx] {{")?; + for (&b, sub_entry) in &entry.sub_entries { + if b.is_ascii_alphabetic() { + writeln!(writer, "{} | {} => {{", b, b.to_ascii_lowercase())?; + } else { + writeln!(writer, "{b} => {{")?; + } + writeln!(writer, "idx += 1;")?; + write_entry(writer, sub_entry)?; + writeln!(writer, "}}")?; + } + + writeln!(writer, "_ => None")?; + writeln!(writer, "}}")?; + Ok(()) + } + + writeln!( + writer, + "pub(crate) const MAX_KEYWORD_LEN: usize = {max_len};" + )?; + writeln!( + writer, + "pub(crate) const MIN_KEYWORD_LEN: usize = {min_len};" + )?; + writeln!(writer, "/// Check if `word` is a keyword")?; + writeln!( + writer, + "pub fn {func_name}(buf: &[u8]) -> Option {{" + )?; + writeln!( + writer, + "if buf.len() < MIN_KEYWORD_LEN || buf.len() > MAX_KEYWORD_LEN {{" + )?; + writeln!(writer, "return None;")?; + writeln!(writer, "}}")?; + writeln!(writer, "let mut idx = 0;")?; + write_entry(writer, &paths)?; + writeln!(writer, "}}")?; + Ok(()) +} + +fn main() { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let keywords = out_dir.join("keywords.rs"); + let mut keywords = BufWriter::new(File::create(keywords).unwrap()); + build_keyword_map( + &mut keywords, + "keyword_token", + &[ + ["ABORT", "TK_ABORT"], + ["ACTION", "TK_ACTION"], + ["ADD", "TK_ADD"], + ["AFTER", "TK_AFTER"], + ["ALL", "TK_ALL"], + ["ALTER", "TK_ALTER"], + ["ALWAYS", "TK_ALWAYS"], + ["ANALYZE", "TK_ANALYZE"], + ["AND", "TK_AND"], + ["AS", "TK_AS"], + ["ASC", "TK_ASC"], + ["ATTACH", "TK_ATTACH"], + ["AUTOINCREMENT", "TK_AUTOINCR"], + ["BEFORE", "TK_BEFORE"], + ["BEGIN", "TK_BEGIN"], + ["BETWEEN", "TK_BETWEEN"], + ["BY", "TK_BY"], + ["CASCADE", "TK_CASCADE"], + ["CASE", "TK_CASE"], + ["CAST", "TK_CAST"], + ["CHECK", "TK_CHECK"], + ["COLLATE", "TK_COLLATE"], + ["COLUMN", "TK_COLUMNKW"], + ["COMMIT", "TK_COMMIT"], + ["CONFLICT", "TK_CONFLICT"], + ["CONSTRAINT", "TK_CONSTRAINT"], + ["CREATE", "TK_CREATE"], + ["CROSS", "TK_JOIN_KW"], + ["CURRENT", "TK_CURRENT"], + ["CURRENT_DATE", "TK_CTIME_KW"], + ["CURRENT_TIME", "TK_CTIME_KW"], + ["CURRENT_TIMESTAMP", "TK_CTIME_KW"], + ["DATABASE", "TK_DATABASE"], + ["DEFAULT", "TK_DEFAULT"], + ["DEFERRABLE", "TK_DEFERRABLE"], + ["DEFERRED", "TK_DEFERRED"], + ["DELETE", "TK_DELETE"], + ["DESC", "TK_DESC"], + ["DETACH", "TK_DETACH"], + ["DISTINCT", "TK_DISTINCT"], + ["DO", "TK_DO"], + ["DROP", "TK_DROP"], + ["EACH", "TK_EACH"], + ["ELSE", "TK_ELSE"], + ["END", "TK_END"], + ["ESCAPE", "TK_ESCAPE"], + ["EXCEPT", "TK_EXCEPT"], + ["EXCLUDE", "TK_EXCLUDE"], + ["EXCLUSIVE", "TK_EXCLUSIVE"], + ["EXISTS", "TK_EXISTS"], + ["EXPLAIN", "TK_EXPLAIN"], + ["FAIL", "TK_FAIL"], + ["FILTER", "TK_FILTER"], + ["FIRST", "TK_FIRST"], + ["FOLLOWING", "TK_FOLLOWING"], + ["FOR", "TK_FOR"], + ["FOREIGN", "TK_FOREIGN"], + ["FROM", "TK_FROM"], + ["FULL", "TK_JOIN_KW"], + ["GENERATED", "TK_GENERATED"], + ["GLOB", "TK_LIKE_KW"], + ["GROUP", "TK_GROUP"], + ["GROUPS", "TK_GROUPS"], + ["HAVING", "TK_HAVING"], + ["IF", "TK_IF"], + ["IGNORE", "TK_IGNORE"], + ["IMMEDIATE", "TK_IMMEDIATE"], + ["IN", "TK_IN"], + ["INDEX", "TK_INDEX"], + ["INDEXED", "TK_INDEXED"], + ["INITIALLY", "TK_INITIALLY"], + ["INNER", "TK_JOIN_KW"], + ["INSERT", "TK_INSERT"], + ["INSTEAD", "TK_INSTEAD"], + ["INTERSECT", "TK_INTERSECT"], + ["INTO", "TK_INTO"], + ["IS", "TK_IS"], + ["ISNULL", "TK_ISNULL"], + ["JOIN", "TK_JOIN"], + ["KEY", "TK_KEY"], + ["LAST", "TK_LAST"], + ["LEFT", "TK_JOIN_KW"], + ["LIKE", "TK_LIKE_KW"], + ["LIMIT", "TK_LIMIT"], + ["MATCH", "TK_MATCH"], + ["MATERIALIZED", "TK_MATERIALIZED"], + ["NATURAL", "TK_JOIN_KW"], + ["NO", "TK_NO"], + ["NOT", "TK_NOT"], + ["NOTHING", "TK_NOTHING"], + ["NOTNULL", "TK_NOTNULL"], + ["NULL", "TK_NULL"], + ["NULLS", "TK_NULLS"], + ["OF", "TK_OF"], + ["OFFSET", "TK_OFFSET"], + ["ON", "TK_ON"], + ["OR", "TK_OR"], + ["ORDER", "TK_ORDER"], + ["OTHERS", "TK_OTHERS"], + ["OUTER", "TK_JOIN_KW"], + ["OVER", "TK_OVER"], + ["PARTITION", "TK_PARTITION"], + ["PLAN", "TK_PLAN"], + ["PRAGMA", "TK_PRAGMA"], + ["PRECEDING", "TK_PRECEDING"], + ["PRIMARY", "TK_PRIMARY"], + ["QUERY", "TK_QUERY"], + ["RAISE", "TK_RAISE"], + ["RANGE", "TK_RANGE"], + ["RECURSIVE", "TK_RECURSIVE"], + ["REFERENCES", "TK_REFERENCES"], + ["REGEXP", "TK_LIKE_KW"], + ["REINDEX", "TK_REINDEX"], + ["RELEASE", "TK_RELEASE"], + ["RENAME", "TK_RENAME"], + ["REPLACE", "TK_REPLACE"], + ["RETURNING", "TK_RETURNING"], + ["RESTRICT", "TK_RESTRICT"], + ["RIGHT", "TK_JOIN_KW"], + ["ROLLBACK", "TK_ROLLBACK"], + ["ROW", "TK_ROW"], + ["ROWS", "TK_ROWS"], + ["SAVEPOINT", "TK_SAVEPOINT"], + ["SELECT", "TK_SELECT"], + ["SET", "TK_SET"], + ["TABLE", "TK_TABLE"], + ["TEMP", "TK_TEMP"], + ["TEMPORARY", "TK_TEMP"], + ["THEN", "TK_THEN"], + ["TIES", "TK_TIES"], + ["TO", "TK_TO"], + ["TRANSACTION", "TK_TRANSACTION"], + ["TRIGGER", "TK_TRIGGER"], + ["UNBOUNDED", "TK_UNBOUNDED"], + ["UNION", "TK_UNION"], + ["UNIQUE", "TK_UNIQUE"], + ["UPDATE", "TK_UPDATE"], + ["USING", "TK_USING"], + ["VACUUM", "TK_VACUUM"], + ["VALUES", "TK_VALUES"], + ["VIEW", "TK_VIEW"], + ["VIRTUAL", "TK_VIRTUAL"], + ["WHEN", "TK_WHEN"], + ["WHERE", "TK_WHERE"], + ["WINDOW", "TK_WINDOW"], + ["WITH", "TK_WITH"], + ["WITHOUT", "TK_WITHOUT"], + ], + ) + .unwrap(); +} diff --git a/parser/src/ast.rs b/parser/src/ast.rs new file mode 100644 index 000000000..55184d990 --- /dev/null +++ b/parser/src/ast.rs @@ -0,0 +1,1417 @@ +use strum_macros::{EnumIter, EnumString}; + +/// `?` or `$` Prepared statement arg placeholder(s) +#[derive(Default)] +pub struct ParameterInfo { + /// Number of SQL parameters in a prepared statement, like `sqlite3_bind_parameter_count` + pub count: u32, + /// Parameter name(s) if any + pub names: Vec, +} + +/// Statement or Explain statement +// https://sqlite.org/syntax/sql-stmt.html +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Cmd { + /// `EXPLAIN` statement + Explain(Stmt), + /// `EXPLAIN QUERY PLAN` statement + ExplainQueryPlan(Stmt), + /// statement + Stmt(Stmt), +} + +/// SQL statement +// https://sqlite.org/syntax/sql-stmt.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Stmt { + /// `ALTER TABLE`: table name, body + AlterTable { + // table name + name: QualifiedName, + // `ALTER TABLE` body + body: AlterTableBody, + }, + /// `ANALYSE`: object name + Analyze { + // object name + name: Option, + }, + /// `ATTACH DATABASE` + Attach { + /// filename + // TODO distinction between ATTACH and ATTACH DATABASE + expr: Box, + /// schema name + db_name: Box, + /// password + key: Option>, + }, + /// `BEGIN`: tx type, tx name + Begin { + // transaction type + typ: Option, + // transaction name + name: Option, + }, + /// `COMMIT`/`END`: tx name + Commit { + // tx name + name: Option, + }, // TODO distinction between COMMIT and END + /// `CREATE INDEX` + CreateIndex { + /// `UNIQUE` + unique: bool, + /// `IF NOT EXISTS` + if_not_exists: bool, + /// index name + idx_name: QualifiedName, + /// table name + tbl_name: Name, + /// indexed columns or expressions + columns: Vec, + /// partial index + where_clause: Option>, + }, + /// `CREATE TABLE` + CreateTable { + /// `TEMPORARY` + temporary: bool, // TODO distinction between TEMP and TEMPORARY + /// `IF NOT EXISTS` + if_not_exists: bool, + /// table name + tbl_name: QualifiedName, + /// table body + body: CreateTableBody, + }, + /// `CREATE TRIGGER` + CreateTrigger { + /// `TEMPORARY` + temporary: bool, + /// `IF NOT EXISTS` + if_not_exists: bool, + /// trigger name + trigger_name: QualifiedName, + /// `BEFORE`/`AFTER`/`INSTEAD OF` + time: Option, + /// `DELETE`/`INSERT`/`UPDATE` + event: TriggerEvent, + /// table name + tbl_name: QualifiedName, + /// `FOR EACH ROW` + for_each_row: bool, + /// `WHEN` + when_clause: Option>, + /// statements + commands: Vec, + }, + /// `CREATE VIEW` + CreateView { + /// `TEMPORARY` + temporary: bool, + /// `IF NOT EXISTS` + if_not_exists: bool, + /// view name + view_name: QualifiedName, + /// columns + columns: Vec, + /// query + select: Select, + }, + /// `CREATE VIRTUAL TABLE` + CreateVirtualTable { + /// `IF NOT EXISTS` + if_not_exists: bool, + /// table name + tbl_name: QualifiedName, + /// module name + module_name: Name, + /// args + args: Vec, // TODO smol str + }, + /// `DELETE` + Delete { + /// CTE + with: Option, + /// `FROM` table name + tbl_name: QualifiedName, + /// `INDEXED` + indexed: Option, + /// `WHERE` clause + where_clause: Option>, + /// `RETURNING` + returning: Vec, + /// `ORDER BY` + order_by: Vec, + /// `LIMIT` + limit: Option, + }, + /// `DETACH DATABASE`: db name + Detach { + // db name + name: Box, + }, // TODO distinction between DETACH and DETACH DATABASE + /// `DROP INDEX` + DropIndex { + /// `IF EXISTS` + if_exists: bool, + /// index name + idx_name: QualifiedName, + }, + /// `DROP TABLE` + DropTable { + /// `IF EXISTS` + if_exists: bool, + /// table name + tbl_name: QualifiedName, + }, + /// `DROP TRIGGER` + DropTrigger { + /// `IF EXISTS` + if_exists: bool, + /// trigger name + trigger_name: QualifiedName, + }, + /// `DROP VIEW` + DropView { + /// `IF EXISTS` + if_exists: bool, + /// view name + view_name: QualifiedName, + }, + /// `INSERT` + Insert { + /// CTE + with: Option, + /// `OR` + or_conflict: Option, // TODO distinction between REPLACE and INSERT OR REPLACE + /// table name + tbl_name: QualifiedName, + /// `COLUMNS` + columns: Vec, + /// `VALUES` or `SELECT` + body: InsertBody, + /// `RETURNING` + returning: Vec, + }, + /// `PRAGMA`: pragma name, body + Pragma { + // pragma name + name: QualifiedName, + // pragma body + body: Option, + }, + /// `REINDEX` + Reindex { + /// collation or index or table name + name: Option, + }, + /// `RELEASE`: savepoint name + Release { + // savepoint name + name: Name, + }, // TODO distinction between RELEASE and RELEASE SAVEPOINT + /// `ROLLBACK` + Rollback { + /// transaction name + tx_name: Option, + /// savepoint name + savepoint_name: Option, // TODO distinction between TO and TO SAVEPOINT + }, + /// `SAVEPOINT`: savepoint name + Savepoint { + // savepoint name + name: Name, + }, + /// `SELECT` + Select(Select), + /// `UPDATE` + Update { + /// CTE + with: Option, + /// `OR` + or_conflict: Option, + /// table name + tbl_name: QualifiedName, + /// `INDEXED` + indexed: Option, + /// `SET` assignments + sets: Vec, + /// `FROM` + from: Option, + /// `WHERE` clause + where_clause: Option>, + /// `RETURNING` + returning: Vec, + /// `ORDER BY` + order_by: Vec, + /// `LIMIT` + limit: Option, + }, + /// `VACUUM`: database name, into expr + Vacuum { + // database name + name: Option, + // into expression + into: Option>, + }, +} + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// Internal ID of a table reference. +/// +/// Used by [Expr::Column] and [Expr::RowId] to refer to a table. +/// E.g. in 'SELECT * FROM t UNION ALL SELECT * FROM t', there are two table references, +/// so there are two TableInternalIds. +/// +/// FIXME: rename this to TableReferenceId. +pub struct TableInternalId(usize); + +/// SQL expression +// https://sqlite.org/syntax/expr.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Expr { + /// `BETWEEN` + Between { + /// expression + lhs: Box, + /// `NOT` + not: bool, + /// start + start: Box, + /// end + end: Box, + }, + /// binary expression + Binary(Box, Operator, Box), + /// `CASE` expression + Case { + /// operand + base: Option>, + /// `WHEN` condition `THEN` result + when_then_pairs: Vec<(Box, Box)>, + /// `ELSE` result + else_expr: Option>, + }, + /// CAST expression + Cast { + /// expression + expr: Box, + /// `AS` type name + type_name: Option, + }, + /// `COLLATE`: expression + Collate(Box, Name), + /// schema-name.table-name.column-name + DoublyQualified(Name, Name, Name), + /// `EXISTS` subquery + Exists(Select), + /// call to a built-in function + FunctionCall { + /// function name + name: Name, + /// `DISTINCT` + distinctness: Option, + /// arguments + args: Vec>, + /// `ORDER BY` + order_by: Vec, + /// `FILTER` + filter_over: FunctionTail, + }, + /// Function call expression with '*' as arg + FunctionCallStar { + /// function name + name: Name, + /// `FILTER` + filter_over: FunctionTail, + }, + /// Identifier + Id(Name), + /// Column + Column { + /// the x in `x.y.z`. index of the db in catalog. + database: Option, + /// the y in `x.y.z`. index of the table in catalog. + table: TableInternalId, + /// the z in `x.y.z`. index of the column in the table. + column: usize, + /// is the column a rowid alias + is_rowid_alias: bool, + }, + /// `ROWID` + RowId { + /// the x in `x.y.z`. index of the db in catalog. + database: Option, + /// the y in `x.y.z`. index of the table in catalog. + table: TableInternalId, + }, + /// `IN` + InList { + /// expression + lhs: Box, + /// `NOT` + not: bool, + /// values + rhs: Vec>, + }, + /// `IN` subselect + InSelect { + /// expression + lhs: Box, + /// `NOT` + not: bool, + /// subquery + rhs: Select, + }, + /// `IN` table name / function + InTable { + /// expression + lhs: Box, + /// `NOT` + not: bool, + /// table name + rhs: QualifiedName, + /// table function arguments + args: Vec>, + }, + /// `IS NULL` + IsNull(Box), + /// `LIKE` + Like { + /// expression + lhs: Box, + /// `NOT` + not: bool, + /// operator + op: LikeOperator, + /// pattern + rhs: Box, + /// `ESCAPE` char + escape: Option>, + }, + /// Literal expression + Literal(Literal), + /// Name + Name(Name), + /// `NOT NULL` or `NOTNULL` + NotNull(Box), + /// Parenthesized subexpression + Parenthesized(Vec>), + /// Qualified name + Qualified(Name, Name), + /// `RAISE` function call + Raise(ResolveType, Option>), + /// Subquery expression + Subquery(Select), + /// Unary expression + Unary(UnaryOperator, Box), + /// Parameters + Variable(String), +} + +/// SQL literal +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Literal { + /// Number + Numeric(String), + /// String + // TODO Check that string is already quoted and correctly escaped + String(String), + /// BLOB + // TODO Check that string is valid (only hexa) + Blob(String), + /// Keyword + Keyword(String), + /// `NULL` + Null, + /// `CURRENT_DATE` + CurrentDate, + /// `CURRENT_TIME` + CurrentTime, + /// `CURRENT_TIMESTAMP` + CurrentTimestamp, +} + +/// Textual comparison operator in an expression +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum LikeOperator { + /// `GLOB` + Glob, + /// `LIKE` + Like, + /// `MATCH` + Match, + /// `REGEXP` + Regexp, +} + +/// SQL operators +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Operator { + /// `+` + Add, + /// `AND` + And, + /// `->` + ArrowRight, + /// `->>` + ArrowRightShift, + /// `&` + BitwiseAnd, + /// `|` + BitwiseOr, + /// `~` + BitwiseNot, + /// String concatenation (`||`) + Concat, + /// `=` or `==` + Equals, + /// `/` + Divide, + /// `>` + Greater, + /// `>=` + GreaterEquals, + /// `IS` + Is, + /// `IS NOT` + IsNot, + /// `<<` + LeftShift, + /// `<` + Less, + /// `<=` + LessEquals, + /// `%` + Modulus, + /// `*` + Multiply, + /// `!=` or `<>` + NotEquals, + /// `OR` + Or, + /// `>>` + RightShift, + /// `-` + Subtract, +} + +/// Unary operators +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum UnaryOperator { + /// bitwise negation (`~`) + BitwiseNot, + /// negative-sign + Negative, + /// `NOT` + Not, + /// positive-sign + Positive, +} + +/// `SELECT` statement +// https://sqlite.org/lang_select.html +// https://sqlite.org/syntax/factored-select-stmt.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Select { + /// CTE + pub with: Option, + /// body + pub body: SelectBody, + /// `ORDER BY` + pub order_by: Vec, // ORDER BY term does not match any column in the result set + /// `LIMIT` + pub limit: Option, +} + +/// `SELECT` body +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SelectBody { + /// first select + pub select: OneSelect, + /// compounds + pub compounds: Vec, +} + +/// Compound select +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CompoundSelect { + /// operator + pub operator: CompoundOperator, + /// select + pub select: OneSelect, +} + +/// Compound operators +// https://sqlite.org/syntax/compound-operator.html +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum CompoundOperator { + /// `UNION` + Union, + /// `UNION ALL` + UnionAll, + /// `EXCEPT` + Except, + /// `INTERSECT` + Intersect, +} + +/// `SELECT` core +// https://sqlite.org/syntax/select-core.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum OneSelect { + /// `SELECT` + Select { + /// `DISTINCT` + distinctness: Option, + /// columns + columns: Vec, + /// `FROM` clause + from: Option, + /// `WHERE` clause + where_clause: Option>, + /// `GROUP BY` + group_by: Option, + /// `WINDOW` definition + window_clause: Vec, + }, + /// `VALUES` + Values(Vec>>), +} + +/// `SELECT` ... `FROM` clause +// https://sqlite.org/syntax/join-clause.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct FromClause { + /// table + pub select: Box, // FIXME mandatory + /// `JOIN`ed tabled + pub joins: Vec, +} + +/// `SELECT` distinctness +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Distinctness { + /// `DISTINCT` + Distinct, + /// `ALL` + All, +} + +/// `SELECT` or `RETURNING` result column +// https://sqlite.org/syntax/result-column.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ResultColumn { + /// expression + Expr(Box, Option), + /// `*` + Star, + /// table name.`*` + TableStar(Name), +} + +/// Alias +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum As { + /// `AS` + As(Name), + /// no `AS` + Elided(Name), // FIXME Ids +} + +/// `JOIN` clause +// https://sqlite.org/syntax/join-clause.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct JoinedSelectTable { + /// operator + pub operator: JoinOperator, + /// table + pub table: Box, + /// constraint + pub constraint: Option, +} + +/// Table or subquery +// https://sqlite.org/syntax/table-or-subquery.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum SelectTable { + /// table + Table(QualifiedName, Option, Option), + /// table function call + TableCall(QualifiedName, Vec>, Option), + /// `SELECT` subquery + Select(Select, Option), + /// subquery + Sub(FromClause, Option), +} + +/// Join operators +// https://sqlite.org/syntax/join-operator.html +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum JoinOperator { + /// `,` + Comma, + /// `JOIN` + TypedJoin(Option), +} + +// https://github.com/sqlite/sqlite/blob/80511f32f7e71062026edd471913ef0455563964/src/select.c#L197-L257 +bitflags::bitflags! { + /// `JOIN` types + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct JoinType: u8 { + /// `INNER` + const INNER = 0x01; + /// `CROSS` => INNER|CROSS + const CROSS = 0x02; + /// `NATURAL` + const NATURAL = 0x04; + /// `LEFT` => LEFT|OUTER + const LEFT = 0x08; + /// `RIGHT` => RIGHT|OUTER + const RIGHT = 0x10; + /// `OUTER` + const OUTER = 0x20; + } +} + +/// `JOIN` constraint +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum JoinConstraint { + /// `ON` + On(Box), + /// `USING`: col names + Using(Vec), +} + +/// `GROUP BY` +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct GroupBy { + /// expressions + pub exprs: Vec>, + /// `HAVING` + pub having: Option>, // HAVING clause on a non-aggregate query +} + +/// identifier or string or `CROSS` or `FULL` or `INNER` or `LEFT` or `NATURAL` or `OUTER` or `RIGHT`. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Name { + /// Identifier + Ident(String), + /// Quoted values + Quoted(String), +} + +/// Qualified name +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct QualifiedName { + /// schema + pub db_name: Option, + /// object name + pub name: Name, + /// alias + pub alias: Option, // FIXME restrict alias usage (fullname vs xfullname) +} + +/// `ALTER TABLE` body +// https://sqlite.org/lang_altertable.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum AlterTableBody { + /// `RENAME TO`: new table name + RenameTo(Name), + /// `ADD COLUMN` + AddColumn(ColumnDefinition), // TODO distinction between ADD and ADD COLUMN + /// `RENAME COLUMN` + RenameColumn { + /// old name + old: Name, + /// new name + new: Name, + }, + /// `DROP COLUMN` + DropColumn(Name), // TODO distinction between DROP and DROP COLUMN +} + +/// `CREATE TABLE` body +// https://sqlite.org/lang_createtable.html +// https://sqlite.org/syntax/create-table-stmt.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum CreateTableBody { + /// columns and constraints + ColumnsAndConstraints { + /// table column definitions + columns: Vec, + /// table constraints + constraints: Vec, + /// table options + options: TableOptions, + }, + /// `AS` select + AsSelect(Select), +} + +/// Table column definition +// https://sqlite.org/syntax/column-def.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ColumnDefinition { + /// column name + pub col_name: Name, + /// column type + pub col_type: Option, + /// column constraints + pub constraints: Vec, +} + +/// Named column constraint +// https://sqlite.org/syntax/column-constraint.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NamedColumnConstraint { + /// constraint name + pub name: Option, + /// constraint + pub constraint: ColumnConstraint, +} + +/// Column constraint +// https://sqlite.org/syntax/column-constraint.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ColumnConstraint { + /// `PRIMARY KEY` + PrimaryKey { + /// `ASC` / `DESC` + order: Option, + /// `ON CONFLICT` clause + conflict_clause: Option, + /// `AUTOINCREMENT` + auto_increment: bool, + }, + /// `NULL` + NotNull { + /// `NOT` + nullable: bool, + /// `ON CONFLICT` clause + conflict_clause: Option, + }, + /// `UNIQUE` + Unique(Option), + /// `CHECK` + Check(Box), + /// `DEFAULT` + Default(Box), + /// `COLLATE` + Collate { + /// collation name + collation_name: Name, // FIXME Ids + }, + /// `REFERENCES` foreign-key clause + ForeignKey { + /// clause + clause: ForeignKeyClause, + /// `DEFERRABLE` + deref_clause: Option, + }, + /// `GENERATED` + Generated { + /// expression + expr: Box, + /// `STORED` / `VIRTUAL` + typ: Option, + }, +} + +/// Named table constraint +// https://sqlite.org/syntax/table-constraint.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NamedTableConstraint { + /// constraint name + pub name: Option, + /// constraint + pub constraint: TableConstraint, +} + +/// Table constraint +// https://sqlite.org/syntax/table-constraint.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TableConstraint { + /// `PRIMARY KEY` + PrimaryKey { + /// columns + columns: Vec, + /// `AUTOINCREMENT` + auto_increment: bool, + /// `ON CONFLICT` clause + conflict_clause: Option, + }, + /// `UNIQUE` + Unique { + /// columns + columns: Vec, + /// `ON CONFLICT` clause + conflict_clause: Option, + }, + /// `CHECK` + Check(Box), + /// `FOREIGN KEY` + ForeignKey { + /// columns + columns: Vec, + /// `REFERENCES` + clause: ForeignKeyClause, + /// `DEFERRABLE` + deref_clause: Option, + }, +} + +bitflags::bitflags! { + /// `CREATE TABLE` options + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct TableOptions: u8 { + /// None + const NONE = 0; + /// `WITHOUT ROWID` + const WITHOUT_ROWID = 1; + /// `STRICT` + const STRICT = 2; + } +} + +/// Sort orders +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum SortOrder { + /// `ASC` + Asc, + /// `DESC` + Desc, +} + +/// `NULLS FIRST` or `NULLS LAST` +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum NullsOrder { + /// `NULLS FIRST` + First, + /// `NULLS LAST` + Last, +} + +/// `REFERENCES` clause +// https://sqlite.org/syntax/foreign-key-clause.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ForeignKeyClause { + /// foreign table name + pub tbl_name: Name, + /// foreign table columns + pub columns: Vec, + /// referential action(s) / deferrable option(s) + pub args: Vec, +} + +/// foreign-key reference args +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum RefArg { + /// `ON DELETE` + OnDelete(RefAct), + /// `ON INSERT` + OnInsert(RefAct), + /// `ON UPDATE` + OnUpdate(RefAct), + /// `MATCH` + Match(Name), +} + +/// foreign-key reference actions +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum RefAct { + /// `SET NULL` + SetNull, + /// `SET DEFAULT` + SetDefault, + /// `CASCADE` + Cascade, + /// `RESTRICT` + Restrict, + /// `NO ACTION` + NoAction, +} + +/// foreign-key defer clause +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DeferSubclause { + /// `DEFERRABLE` + pub deferrable: bool, + /// `INITIALLY` `DEFERRED` / `IMMEDIATE` + pub init_deferred: Option, +} + +/// `INITIALLY` `DEFERRED` / `IMMEDIATE` +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum InitDeferredPred { + /// `INITIALLY DEFERRED` + InitiallyDeferred, + /// `INITIALLY IMMEDIATE` + InitiallyImmediate, // default +} + +/// Indexed column +// https://sqlite.org/syntax/indexed-column.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct IndexedColumn { + /// column name + pub col_name: Name, + /// `COLLATE` + pub collation_name: Option, // FIXME Ids + /// `ORDER BY` + pub order: Option, +} + +/// `INDEXED BY` / `NOT INDEXED` +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Indexed { + /// `INDEXED BY`: idx name + IndexedBy(Name), + /// `NOT INDEXED` + NotIndexed, +} + +/// Sorted column +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SortedColumn { + /// expression + pub expr: Box, + /// `ASC` / `DESC` + pub order: Option, + /// `NULLS FIRST` / `NULLS LAST` + pub nulls: Option, +} + +/// `LIMIT` +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Limit { + /// count + pub expr: Box, + /// `OFFSET` + pub offset: Option>, // TODO distinction between LIMIT offset, count and LIMIT count OFFSET offset +} + +/// `INSERT` body +// https://sqlite.org/lang_insert.html +// https://sqlite.org/syntax/insert-stmt.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[allow(clippy::large_enum_variant)] +pub enum InsertBody { + /// `SELECT` or `VALUES` + Select(Select, Option>), + /// `DEFAULT VALUES` + DefaultValues, +} + +/// `UPDATE ... SET` +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Set { + /// column name(s) + pub col_names: Vec, + /// expression + pub expr: Box, +} + +/// `PRAGMA` body +// https://sqlite.org/syntax/pragma-stmt.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum PragmaBody { + /// `=` + Equals(PragmaValue), + /// function call + Call(PragmaValue), +} + +/// `PRAGMA` value +// https://sqlite.org/syntax/pragma-value.html +pub type PragmaValue = Box; // TODO + +/// `PRAGMA` value +// https://sqlite.org/pragma.html +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, EnumString, strum::Display)] +#[strum(serialize_all = "snake_case")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum PragmaName { + /// Returns the application ID of the database file. + ApplicationId, + /// set the autovacuum mode + AutoVacuum, + /// `cache_size` pragma + CacheSize, + /// List databases + DatabaseList, + /// Encoding - only support utf8 + Encoding, + /// Run integrity check on the database file + IntegrityCheck, + /// `journal_mode` pragma + JournalMode, + /// Noop as per SQLite docs + LegacyFileFormat, + /// Return the total number of pages in the database file. + PageCount, + /// Return the page size of the database in bytes. + PageSize, + /// Returns schema version of the database file. + SchemaVersion, + /// returns information about the columns of a table + TableInfo, + /// enable capture-changes logic for the connection + UnstableCaptureDataChangesConn, + /// Returns the user version of the database file. + UserVersion, + /// trigger a checkpoint to run on database(s) if WAL is enabled + WalCheckpoint, +} + +/// `CREATE TRIGGER` time +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TriggerTime { + /// `BEFORE` + Before, // default + /// `AFTER` + After, + /// `INSTEAD OF` + InsteadOf, +} + +/// `CREATE TRIGGER` event +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TriggerEvent { + /// `DELETE` + Delete, + /// `INSERT` + Insert, + /// `UPDATE` + Update, + /// `UPDATE OF`: col names + UpdateOf(Vec), +} + +/// `CREATE TRIGGER` command +// https://sqlite.org/lang_createtrigger.html +// https://sqlite.org/syntax/create-trigger-stmt.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TriggerCmd { + /// `UPDATE` + Update { + /// `OR` + or_conflict: Option, + /// table name + tbl_name: Name, + /// `SET` assignments + sets: Vec, + /// `FROM` + from: Option, + /// `WHERE` clause + where_clause: Option>, + }, + /// `INSERT` + Insert { + /// `OR` + or_conflict: Option, + /// table name + tbl_name: Name, + /// `COLUMNS` + col_names: Vec, + /// `SELECT` or `VALUES` + select: Select, + /// `ON CONFLICT` clause + upsert: Option>, + /// `RETURNING` + returning: Vec, + }, + /// `DELETE` + Delete { + /// table name + tbl_name: Name, + /// `WHERE` clause + where_clause: Option>, + }, + /// `SELECT` + Select(Select), +} + +/// Conflict resolution types +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ResolveType { + /// `ROLLBACK` + Rollback, + /// `ABORT` + Abort, // default + /// `FAIL` + Fail, + /// `IGNORE` + Ignore, + /// `REPLACE` + Replace, +} + +/// `WITH` clause +// https://sqlite.org/lang_with.html +// https://sqlite.org/syntax/with-clause.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct With { + /// `RECURSIVE` + pub recursive: bool, + /// CTEs + pub ctes: Vec, +} + +/// CTE materialization +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Materialized { + /// No hint + Any, + /// `MATERIALIZED` + Yes, + /// `NOT MATERIALIZED` + No, +} + +/// CTE +// https://sqlite.org/syntax/common-table-expression.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CommonTableExpr { + /// table name + pub tbl_name: Name, + /// table columns + pub columns: Vec, // check no duplicate + /// `MATERIALIZED` + pub materialized: Materialized, + /// query + pub select: Select, +} + +/// Column type +// https://sqlite.org/syntax/type-name.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Type { + /// type name + pub name: String, // TODO Validate: Ids+ + /// type size + pub size: Option, +} + +/// Column type size limit(s) +// https://sqlite.org/syntax/type-name.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TypeSize { + /// maximum size + MaxSize(Box), + /// precision + TypeSize(Box, Box), +} + +/// Transaction types +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TransactionType { + /// `DEFERRED` + Deferred, // default + /// `IMMEDIATE` + Immediate, + /// `EXCLUSIVE` + Exclusive, +} + +/// Upsert clause +// https://sqlite.org/lang_upsert.html +// https://sqlite.org/syntax/upsert-clause.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Upsert { + /// conflict targets + pub index: Option, + /// `DO` clause + pub do_clause: UpsertDo, + /// next upsert + pub next: Option>, +} + +/// Upsert conflict targets +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct UpsertIndex { + /// columns + pub targets: Vec, + /// `WHERE` clause + pub where_clause: Option>, +} + +/// Upsert `DO` action +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum UpsertDo { + /// `SET` + Set { + /// assignments + sets: Vec, + /// `WHERE` clause + where_clause: Option>, + }, + /// `NOTHING` + Nothing, +} + +/// Function call tail +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct FunctionTail { + /// `FILTER` clause + pub filter_clause: Option>, + /// `OVER` clause + pub over_clause: Option, +} + +/// Function call `OVER` clause +// https://sqlite.org/syntax/over-clause.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Over { + /// Window definition + Window(Window), + /// Window name + Name(Name), +} + +/// `OVER` window definition +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WindowDef { + /// window name + pub name: Name, + /// window definition + pub window: Window, +} + +/// Window definition +// https://sqlite.org/syntax/window-defn.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Window { + /// base window name + pub base: Option, + /// `PARTITION BY` + pub partition_by: Vec>, + /// `ORDER BY` + pub order_by: Vec, + /// frame spec + pub frame_clause: Option, +} + +/// Frame specification +// https://sqlite.org/syntax/frame-spec.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct FrameClause { + /// unit + pub mode: FrameMode, + /// start bound + pub start: FrameBound, + /// end bound + pub end: Option, + /// `EXCLUDE` + pub exclude: Option, +} + +/// Frame modes +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum FrameMode { + /// `GROUPS` + Groups, + /// `RANGE` + Range, + /// `ROWS` + Rows, +} + +/// Frame bounds +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum FrameBound { + /// `CURRENT ROW` + CurrentRow, + /// `FOLLOWING` + Following(Box), + /// `PRECEDING` + Preceding(Box), + /// `UNBOUNDED FOLLOWING` + UnboundedFollowing, + /// `UNBOUNDED PRECEDING` + UnboundedPreceding, +} + +/// Frame exclusions +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum FrameExclude { + /// `NO OTHERS` + NoOthers, + /// `CURRENT ROW` + CurrentRow, + /// `GROUP` + Group, + /// `TIES` + Ties, +} diff --git a/parser/src/error.rs b/parser/src/error.rs new file mode 100644 index 000000000..270874f1b --- /dev/null +++ b/parser/src/error.rs @@ -0,0 +1,93 @@ +use std::error; +use std::fmt; + +use crate::token::TokenType; + +/// SQL lexer and parser errors +#[non_exhaustive] +#[derive(Debug, miette::Diagnostic)] +#[diagnostic()] +pub enum Error { + /// Lexer error + UnrecognizedToken(#[label("here")] miette::SourceSpan), + /// Missing quote or double-quote or backtick + UnterminatedLiteral(#[label("here")] miette::SourceSpan), + /// Missing `]` + UnterminatedBracket(#[label("here")] miette::SourceSpan), + /// Missing `*/` + UnterminatedBlockComment(#[label("here")] miette::SourceSpan), + /// Invalid parameter name + BadVariableName(#[label("here")] miette::SourceSpan), + /// Invalid number format + BadNumber(#[label("here")] miette::SourceSpan), + // Bad fractional part of a number + BadFractionalPart(#[label("here")] miette::SourceSpan), + // Bad exponent part of a number + BadExponentPart(#[label("here")] miette::SourceSpan), + /// Invalid or missing sign after `!` + ExpectedEqualsSign(#[label("here")] miette::SourceSpan), + /// Hexadecimal integer literals follow the C-language notation of "0x" or "0X" followed by hexadecimal digits. + MalformedHexInteger(#[label("here")] miette::SourceSpan), + // parse errors + // Unexpected end of file + ParseUnexpectedEOF, + // Unexpected token + ParseUnexpectedToken { + #[label("parsed to here")] + parsed_offset: miette::SourceSpan, + + got: TokenType, + expected: &'static [TokenType], + }, + // Custom error message + Custom(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::UnrecognizedToken(pos) => { + write!(f, "unrecognized token at {pos:?}") + } + Self::UnterminatedLiteral(pos) => { + write!(f, "non-terminated literal at {pos:?}") + } + Self::UnterminatedBracket(pos) => { + write!(f, "non-terminated bracket at {pos:?}") + } + Self::UnterminatedBlockComment(pos) => { + write!(f, "non-terminated block comment at {pos:?}") + } + Self::BadVariableName(pos) => write!(f, "bad variable name at {pos:?}"), + Self::BadNumber(pos) => write!(f, "bad number at {pos:?}"), + Self::BadFractionalPart(pos) => { + write!(f, "bad fractional part at {pos:?}") + } + Self::BadExponentPart(pos) => { + write!(f, "bad exponent part at {pos:?}") + } + Self::ExpectedEqualsSign(pos) => write!(f, "expected = sign at {pos:?}"), + Self::MalformedHexInteger(pos) => { + write!(f, "malformed hex integer at {pos:?}") + } + Self::ParseUnexpectedEOF => { + write!(f, "unexpected end of file") + } + Self::ParseUnexpectedToken { + parsed_offset, + got, + expected, + } => { + write!( + f, + "got unexpected token after parsing to offset {parsed_offset:?}: expected {expected:?}, found {got}", + ) + } + Self::Custom(ref s) => { + write!(f, "custom error: {s}") + } + } + } +} + +impl error::Error for Error {} diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs new file mode 100644 index 000000000..636352fa5 --- /dev/null +++ b/parser/src/lexer.rs @@ -0,0 +1,1380 @@ +use crate::{error::Error, token::TokenType}; + +include!(concat!(env!("OUT_DIR"), "/keywords.rs")); + +#[inline(always)] +pub fn is_identifier_start(b: u8) -> bool { + b.is_ascii_uppercase() || b == b'_' || b.is_ascii_lowercase() || b > b'\x7F' +} + +#[inline(always)] +pub fn is_identifier_continue(b: u8) -> bool { + b == b'$' + || b.is_ascii_digit() + || b.is_ascii_uppercase() + || b == b'_' + || b.is_ascii_lowercase() + || b > b'\x7F' +} + +#[derive(Clone, PartialEq, Eq)] // do not derive Copy for Token, just use .clone() when needed +pub struct Token<'a> { + pub value: &'a [u8], + pub token_type: Option, // None means Token is whitespaces or comments +} + +pub struct Lexer<'a> { + pub(crate) offset: usize, + pub(crate) input: &'a [u8], +} + +impl<'a> Iterator for Lexer<'a> { + type Item = Result, Error>; + + #[inline] + fn next(&mut self) -> Option { + match self.peek() { + None => None, // End of file + Some(b) if b.is_ascii_whitespace() => Some(Ok(self.eat_white_space())), + // matching logic + Some(b) => match b { + b'-' => Some(Ok(self.eat_minus_or_comment_or_ptr())), + b'(' => Some(Ok(self.eat_one_token(TokenType::TK_LP))), + b')' => Some(Ok(self.eat_one_token(TokenType::TK_RP))), + b';' => Some(Ok(self.eat_one_token(TokenType::TK_SEMI))), + b'+' => Some(Ok(self.eat_one_token(TokenType::TK_PLUS))), + b'*' => Some(Ok(self.eat_one_token(TokenType::TK_STAR))), + b'/' => Some(self.mark(|l| l.eat_slash_or_comment())), + b'%' => Some(Ok(self.eat_one_token(TokenType::TK_REM))), + b'=' => Some(Ok(self.eat_eq())), + b'<' => Some(Ok(self.eat_le_or_ne_or_lshift_or_lt())), + b'>' => Some(Ok(self.eat_ge_or_gt_or_rshift())), + b'!' => Some(self.mark(|l| l.eat_ne())), + b'|' => Some(Ok(self.eat_concat_or_bitor())), + b',' => Some(Ok(self.eat_one_token(TokenType::TK_COMMA))), + b'&' => Some(Ok(self.eat_one_token(TokenType::TK_BITAND))), + b'~' => Some(Ok(self.eat_one_token(TokenType::TK_BITNOT))), + b'\'' | b'"' | b'`' => Some(self.mark(|l| l.eat_lit_or_id())), + b'.' => Some(self.mark(|l| l.eat_dot_or_frac())), + b'0'..=b'9' => Some(self.mark(|l| l.eat_number())), + b'[' => Some(self.mark(|l| l.eat_bracket())), + b'?' | b'$' | b'@' | b'#' | b':' => Some(self.mark(|l| l.eat_var())), + b if is_identifier_start(b) => Some(self.mark(|l| l.eat_blob_or_id())), + _ => Some(Ok(self.eat_unrecognized())), + }, + } + } +} + +impl<'a> Lexer<'a> { + #[inline(always)] + pub fn new(input: &'a [u8]) -> Self { + Lexer { input, offset: 0 } + } + + #[inline(always)] + pub fn remaining(&self) -> &'a [u8] { + &self.input[self.offset..] + } + + #[inline] + pub fn mark(&mut self, exc: F) -> Result + where + F: FnOnce(&mut Self) -> Result, + { + let start_offset = self.offset; + let result = exc(self); + if result.is_err() { + self.offset = start_offset; // Reset to the start offset if an error occurs + } + result + } + + /// Returns the current offset in the input without consuming. + #[inline(always)] + pub fn peek(&self) -> Option { + if self.offset < self.input.len() { + Some(self.input[self.offset]) + } else { + None // End of file + } + } + + /// Returns the current offset in the input and consumes it. + #[inline(always)] + pub fn eat(&mut self) -> Option { + let result = self.peek(); + if result.is_some() { + self.offset += 1; + } + + result + } + + #[inline(always)] + fn eat_and_assert(&mut self, f: F) + where + F: Fn(u8) -> bool, + { + let _value = self.eat(); + debug_assert!(f(_value.unwrap())) + } + + #[inline] + fn eat_while(&mut self, f: F) + where + F: Fn(Option) -> bool, + { + loop { + if !f(self.peek()) { + return; + } + + self.eat(); + } + } + + fn eat_while_number_digit(&mut self) -> Result<(), Error> { + loop { + let start = self.offset; + self.eat_while(|b| b.is_some() && b.unwrap().is_ascii_digit()); + match self.peek() { + Some(b'_') => { + if start == self.offset { + // before the underscore, there was no digit + return Err(Error::BadNumber((start, self.offset - start).into())); + } + + self.eat_and_assert(|b| b == b'_'); + match self.peek() { + Some(b) if b.is_ascii_digit() => continue, // Continue if next is a digit + _ => { + // after the underscore, there is no digit + return Err(Error::BadNumber((start, self.offset - start).into())); + } + } + } + _ => return Ok(()), + } + } + } + + fn eat_while_number_hexdigit(&mut self) -> Result<(), Error> { + loop { + let start = self.offset; + self.eat_while(|b| b.is_some() && b.unwrap().is_ascii_hexdigit()); + match self.peek() { + Some(b'_') => { + if start == self.offset { + // before the underscore, there was no digit + return Err(Error::BadNumber((start, self.offset - start).into())); + } + + self.eat_and_assert(|b| b == b'_'); + match self.peek() { + Some(b) if b.is_ascii_hexdigit() => continue, // Continue if next is a digit + _ => { + // after the underscore, there is no digit + return Err(Error::BadNumber((start, self.offset - start).into())); + } + } + } + _ => return Ok(()), + } + } + } + + #[inline] + fn eat_one_token(&mut self, typ: TokenType) -> Token<'a> { + debug_assert!(!self.remaining().is_empty()); + + let tok = Token { + value: &self.remaining()[..1], + token_type: Some(typ), + }; + self.offset += 1; + tok + } + + #[inline] + fn eat_white_space(&mut self) -> Token<'a> { + let start = self.offset; + self.eat_and_assert(|b| b.is_ascii_whitespace()); + self.eat_while(|b| b.is_some() && b.unwrap().is_ascii_whitespace()); + Token { + value: &self.input[start..self.offset], + token_type: None, // This is a whitespace + } + } + + fn eat_minus_or_comment_or_ptr(&mut self) -> Token<'a> { + let start = self.offset; + self.eat_and_assert(|b| b == b'-'); + + match self.peek() { + Some(b'-') => { + self.eat_and_assert(|b| b == b'-'); + self.eat_while(|b| b.is_some() && b.unwrap() != b'\n'); + if self.peek() == Some(b'\n') { + self.eat_and_assert(|b| b == b'\n'); + } + + Token { + value: &self.input[start..self.offset], + token_type: None, // This is a comment + } + } + Some(b'>') => { + self.eat_and_assert(|b| b == b'>'); + if self.peek() == Some(b'>') { + self.eat_and_assert(|b| b == b'>'); + } + + Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_PTR), + } + } + _ => Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_MINUS), + }, + } + } + + fn eat_slash_or_comment(&mut self) -> Result, Error> { + let start = self.offset; + self.eat_and_assert(|b| b == b'/'); + match self.peek() { + Some(b'*') => { + self.eat_and_assert(|b| b == b'*'); + loop { + self.eat_while(|b| b.is_some() && b.unwrap() != b'*'); + match self.peek() { + Some(b'*') => { + self.eat_and_assert(|b| b == b'*'); + match self.peek() { + Some(b'/') => { + self.eat_and_assert(|b| b == b'/'); + break; // End of block comment + } + None => { + return Err(Error::UnterminatedBlockComment( + (start, self.offset - start).into(), + )) + } + _ => {} + } + } + None => { + return Err(Error::UnterminatedBlockComment( + (start, self.offset - start).into(), + )) + } + _ => unreachable!(), // We should not reach here + } + } + + Ok(Token { + value: &self.input[start..self.offset], + token_type: None, // This is a comment + }) + } + _ => Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_SLASH), + }), + } + } + + fn eat_eq(&mut self) -> Token<'a> { + let start = self.offset; + self.eat_and_assert(|b| b == b'='); + if self.peek() == Some(b'=') { + self.eat_and_assert(|b| b == b'='); + } + + Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_EQ), + } + } + + fn eat_le_or_ne_or_lshift_or_lt(&mut self) -> Token<'a> { + let start = self.offset; + self.eat_and_assert(|b| b == b'<'); + match self.peek() { + Some(b'=') => { + self.eat_and_assert(|b| b == b'='); + Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_LE), + } + } + Some(b'<') => { + self.eat_and_assert(|b| b == b'<'); + Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_LSHIFT), + } + } + Some(b'>') => { + self.eat_and_assert(|b| b == b'>'); + Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_NE), + } + } + _ => Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_LT), + }, + } + } + + fn eat_ge_or_gt_or_rshift(&mut self) -> Token<'a> { + let start = self.offset; + self.eat_and_assert(|b| b == b'>'); + match self.peek() { + Some(b'=') => { + self.eat_and_assert(|b| b == b'='); + Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_GE), + } + } + Some(b'>') => { + self.eat_and_assert(|b| b == b'>'); + Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_RSHIFT), + } + } + _ => Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_GT), + }, + } + } + + fn eat_ne(&mut self) -> Result, Error> { + let start = self.offset; + self.eat_and_assert(|b| b == b'!'); + match self.peek() { + Some(b'=') => { + self.eat_and_assert(|b| b == b'='); + } + _ => { + return Err(Error::ExpectedEqualsSign( + (start, self.offset - start).into(), + )) + } + } + + Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_NE), + }) + } + + fn eat_concat_or_bitor(&mut self) -> Token<'a> { + let start = self.offset; + self.eat_and_assert(|b| b == b'|'); + if self.peek() == Some(b'|') { + self.eat_and_assert(|b| b == b'|'); + return Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_CONCAT), + }; + } + + Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_BITOR), + } + } + + fn eat_lit_or_id(&mut self) -> Result, Error> { + let start = self.offset; + let quote = self.eat().unwrap(); + debug_assert!(quote == b'\'' || quote == b'"' || quote == b'`'); + let tt = if quote == b'\'' { + TokenType::TK_STRING + } else { + TokenType::TK_ID + }; + + loop { + self.eat_while(|b| b.is_some() && b.unwrap() != quote); + match self.peek() { + Some(b) if b == quote => { + self.eat_and_assert(|b| b == quote); + match self.peek() { + Some(b) if b == quote => { + self.eat_and_assert(|b| b == quote); + continue; + } + _ => break, + } + } + None => { + return Err(Error::UnterminatedLiteral( + (start, self.offset - start).into(), + )) + } + _ => unreachable!(), + }; + } + + Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(tt), + }) + } + + fn eat_dot_or_frac(&mut self) -> Result, Error> { + let start = self.offset; + self.eat_and_assert(|b| b == b'.'); + + match self.peek() { + Some(b) if b.is_ascii_digit() => { + self.eat_while_number_digit()?; + match self.peek() { + Some(b'e') | Some(b'E') => { + _ = self.eat_expo()?; + Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_FLOAT), + }) + } + Some(b) if is_identifier_start(b) => Err(Error::BadFractionalPart( + (start, self.offset - start).into(), + )), + _ => Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_FLOAT), + }), + } + } + _ => Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_DOT), + }), + } + } + + fn eat_expo(&mut self) -> Result, Error> { + let start = self.offset; + self.eat_and_assert(|b| b == b'e' || b == b'E'); + match self.peek() { + Some(b'+') | Some(b'-') => { + self.eat_and_assert(|b| b == b'+' || b == b'-'); + } + _ => {} + } + + let start_num = self.offset; + self.eat_while_number_digit()?; + if start_num == self.offset { + return Err(Error::BadExponentPart((start, self.offset - start).into())); + } + + if self.peek().is_some() && is_identifier_start(self.peek().unwrap()) { + return Err(Error::BadExponentPart((start, self.offset - start).into())); + } + + Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_FLOAT), // This is a number + }) + } + + fn eat_number(&mut self) -> Result, Error> { + let start = self.offset; + let first_digit = self.eat().unwrap(); + debug_assert!(first_digit.is_ascii_digit()); + + // hex int + if first_digit == b'0' { + match self.peek() { + Some(b'x') | Some(b'X') => { + self.eat_and_assert(|b| b == b'x' || b == b'X'); + let start_hex = self.offset; + self.eat_while_number_hexdigit()?; + + if start_hex == self.offset { + return Err(Error::MalformedHexInteger( + (start, self.offset - start).into(), + )); + } + + if self.peek().is_some() && is_identifier_start(self.peek().unwrap()) { + return Err(Error::BadNumber((start, self.offset - start).into())); + } + + return Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_INTEGER), + }); + } + _ => {} + } + } + + self.eat_while_number_digit()?; + match self.peek() { + Some(b'.') => { + self.eat_dot_or_frac()?; + Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_FLOAT), + }) + } + Some(b'e') | Some(b'E') => { + self.eat_expo()?; + Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_FLOAT), + }) + } + Some(b) if is_identifier_start(b) => { + Err(Error::BadNumber((start, self.offset - start).into())) + } + _ => Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_INTEGER), + }), + } + } + + fn eat_bracket(&mut self) -> Result, Error> { + let start = self.offset; + self.eat_and_assert(|b| b == b'['); + self.eat_while(|b| b.is_some() && b.unwrap() != b']'); + match self.peek() { + Some(b']') => { + self.eat_and_assert(|b| b == b']'); + Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_ID), + }) + } + None => Err(Error::UnterminatedBracket( + (start, self.offset - start).into(), + )), + _ => unreachable!(), // We should not reach here + } + } + + fn eat_var(&mut self) -> Result, Error> { + let start = self.offset; + let tok = self.eat().unwrap(); + debug_assert!(tok == b'?' || tok == b'$' || tok == b'@' || tok == b'#' || tok == b':'); + + match tok { + b'?' => { + let start_digit = self.offset; + self.eat_while(|b| b.is_some() && b.unwrap().is_ascii_digit()); + + // empty variable name + if start_digit == self.offset { + return Err(Error::BadVariableName((start, self.offset - start).into())); + } + + Ok(Token { + value: &self.input[start + 1..self.offset], // do not include '? in the value + token_type: Some(TokenType::TK_VARIABLE), + }) + } + _ => { + let start_id = self.offset; + self.eat_while(|b| b.is_some() && is_identifier_continue(b.unwrap())); + + // empty variable name + if start_id == self.offset { + return Err(Error::BadVariableName((start, self.offset - start).into())); + } + + Ok(Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_VARIABLE), + }) + } + } + } + + #[inline] + fn eat_blob_or_id(&mut self) -> Result, Error> { + let start = self.offset; + let start_char = self.eat().unwrap(); + debug_assert!(is_identifier_start(start_char)); + + match start_char { + b'x' | b'X' if self.peek() == Some(b'\'') => { + self.eat_and_assert(|b| b == b'\''); + let start_hex = self.offset; + self.eat_while(|b| b.is_some() && b.unwrap().is_ascii_hexdigit()); + + match self.peek() { + Some(b'\'') => { + let end_hex = self.offset; + debug_assert!(end_hex >= start_hex); + self.eat_and_assert(|b| b == b'\''); + + if (end_hex - start_hex) % 2 != 0 { + return Err(Error::UnrecognizedToken( + (start, self.offset - start).into(), + )); + } + + Ok(Token { + value: &self.input[start + 2..self.offset - 1], // do not include 'x' or 'X' and the last ' + token_type: Some(TokenType::TK_BLOB), + }) + } + _ => Err(Error::UnterminatedLiteral( + (start, self.offset - start).into(), + )), + } + } + _ => { + self.eat_while(|b| b.is_some() && is_identifier_continue(b.unwrap())); + let result = &self.input[start..self.offset]; + Ok(Token { + value: result, + token_type: Some(keyword_token(result).unwrap_or(TokenType::TK_ID)), + }) + } + } + } + + fn eat_unrecognized(&mut self) -> Token<'a> { + let start = self.offset; + self.eat_while(|b| b.is_some() && !b.unwrap().is_ascii_whitespace()); + Token { + value: &self.input[start..self.offset], + token_type: Some(TokenType::TK_ILLEGAL), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + + #[test] + fn test_lexer_one_tok() { + let test_cases = vec![ + ( + b" ".as_slice(), + Token { + value: b" ".as_slice(), + token_type: None, + }, + ), + ( + b"-- This is a comment\n".as_slice(), + Token { + value: b"-- This is a comment\n".as_slice(), + token_type: None, // This is a comment + }, + ), + ( + b"-".as_slice(), + Token { + value: b"-".as_slice(), + token_type: Some(TokenType::TK_MINUS), + }, + ), + ( + b"->".as_slice(), + Token { + value: b"->".as_slice(), + token_type: Some(TokenType::TK_PTR), + }, + ), + ( + b"->>".as_slice(), + Token { + value: b"->>".as_slice(), + token_type: Some(TokenType::TK_PTR), + }, + ), + ( + b"(".as_slice(), + Token { + value: b"(".as_slice(), + token_type: Some(TokenType::TK_LP), + }, + ), + ( + b")".as_slice(), + Token { + value: b")".as_slice(), + token_type: Some(TokenType::TK_RP), + }, + ), + ( + b";".as_slice(), + Token { + value: b";".as_slice(), + token_type: Some(TokenType::TK_SEMI), + }, + ), + ( + b"+".as_slice(), + Token { + value: b"+".as_slice(), + token_type: Some(TokenType::TK_PLUS), + }, + ), + ( + b"*".as_slice(), + Token { + value: b"*".as_slice(), + token_type: Some(TokenType::TK_STAR), + }, + ), + ( + b"/".as_slice(), + Token { + value: b"/".as_slice(), + token_type: Some(TokenType::TK_SLASH), + }, + ), + ( + b"/* This is a block comment */".as_slice(), + Token { + value: b"/* This is a block comment */".as_slice(), + token_type: None, // This is a comment + }, + ), + ( + b"/* This is a\n\n block comment */".as_slice(), + Token { + value: b"/* This is a\n\n block comment */".as_slice(), + token_type: None, // This is a comment + }, + ), + ( + b"/* This is a** block* comment */".as_slice(), + Token { + value: b"/* This is a** block* comment */".as_slice(), + token_type: None, // This is a comment + }, + ), + ( + b"=".as_slice(), + Token { + value: b"=".as_slice(), + token_type: Some(TokenType::TK_EQ), + }, + ), + ( + b"==".as_slice(), + Token { + value: b"==".as_slice(), + token_type: Some(TokenType::TK_EQ), + }, + ), + ( + b"<".as_slice(), + Token { + value: b"<".as_slice(), + token_type: Some(TokenType::TK_LT), + }, + ), + ( + b"<>".as_slice(), + Token { + value: b"<>".as_slice(), + token_type: Some(TokenType::TK_NE), + }, + ), + ( + b"<=".as_slice(), + Token { + value: b"<=".as_slice(), + token_type: Some(TokenType::TK_LE), + }, + ), + ( + b"<<".as_slice(), + Token { + value: b"<<".as_slice(), + token_type: Some(TokenType::TK_LSHIFT), + }, + ), + ( + b">".as_slice(), + Token { + value: b">".as_slice(), + token_type: Some(TokenType::TK_GT), + }, + ), + ( + b">=".as_slice(), + Token { + value: b">=".as_slice(), + token_type: Some(TokenType::TK_GE), + }, + ), + ( + b">>".as_slice(), + Token { + value: b">>".as_slice(), + token_type: Some(TokenType::TK_RSHIFT), + }, + ), + ( + b"!=".as_slice(), + Token { + value: b"!=".as_slice(), + token_type: Some(TokenType::TK_NE), + }, + ), + ( + b"|".as_slice(), + Token { + value: b"|".as_slice(), + token_type: Some(TokenType::TK_BITOR), + }, + ), + ( + b"||".as_slice(), + Token { + value: b"||".as_slice(), + token_type: Some(TokenType::TK_CONCAT), + }, + ), + ( + b",".as_slice(), + Token { + value: b",".as_slice(), + token_type: Some(TokenType::TK_COMMA), + }, + ), + ( + b"&".as_slice(), + Token { + value: b"&".as_slice(), + token_type: Some(TokenType::TK_BITAND), + }, + ), + ( + b"~".as_slice(), + Token { + value: b"~".as_slice(), + token_type: Some(TokenType::TK_BITNOT), + }, + ), + ( + b"'string'".as_slice(), + Token { + value: b"'string'".as_slice(), + token_type: Some(TokenType::TK_STRING), + }, + ), + ( + b"`identifier`".as_slice(), + Token { + value: b"`identifier`".as_slice(), + token_type: Some(TokenType::TK_ID), + }, + ), + ( + b"\"quoted string\"".as_slice(), + Token { + value: b"\"quoted string\"".as_slice(), + token_type: Some(TokenType::TK_ID), + }, + ), + ( + b"\"\"\"triple \"\"quoted string\"\"\"".as_slice(), + Token { + value: b"\"\"\"triple \"\"quoted string\"\"\"".as_slice(), + token_type: Some(TokenType::TK_ID), + }, + ), + ( + b"```triple ``quoted string```".as_slice(), + Token { + value: b"```triple ``quoted string```".as_slice(), + token_type: Some(TokenType::TK_ID), + }, + ), + ( + b"'''triple ''quoted string'''".as_slice(), + Token { + value: b"'''triple ''quoted string'''".as_slice(), + token_type: Some(TokenType::TK_STRING), + }, + ), + ( + b".".as_slice(), + Token { + value: b".".as_slice(), + token_type: Some(TokenType::TK_DOT), + }, + ), + ( + b".123".as_slice(), + Token { + value: b".123".as_slice(), + token_type: Some(TokenType::TK_FLOAT), + }, + ), + ( + b".456".as_slice(), + Token { + value: b".456".as_slice(), + token_type: Some(TokenType::TK_FLOAT), + }, + ), + ( + b".456e789".as_slice(), + Token { + value: b".456e789".as_slice(), + token_type: Some(TokenType::TK_FLOAT), + }, + ), + ( + b".456E-789".as_slice(), + Token { + value: b".456E-789".as_slice(), + token_type: Some(TokenType::TK_FLOAT), + }, + ), + ( + b"123".as_slice(), + Token { + value: b"123".as_slice(), + token_type: Some(TokenType::TK_INTEGER), + }, + ), + ( + b"123.456".as_slice(), + Token { + value: b"123.456".as_slice(), + token_type: Some(TokenType::TK_FLOAT), + }, + ), + ( + b"123e456".as_slice(), + Token { + value: b"123e456".as_slice(), + token_type: Some(TokenType::TK_FLOAT), + }, + ), + ( + b"123E-456".as_slice(), + Token { + value: b"123E-456".as_slice(), + token_type: Some(TokenType::TK_FLOAT), + }, + ), + ( + b"0x1A3F".as_slice(), + Token { + value: b"0x1A3F".as_slice(), + token_type: Some(TokenType::TK_INTEGER), + }, + ), + ( + b"0x1A3F_5678".as_slice(), + Token { + value: b"0x1A3F_5678".as_slice(), + token_type: Some(TokenType::TK_INTEGER), + }, + ), + ( + b"0x1A3F_5678e9".as_slice(), + Token { + value: b"0x1A3F_5678e9".as_slice(), + token_type: Some(TokenType::TK_INTEGER), + }, + ), + ( + b"[identifier]".as_slice(), + Token { + value: b"[identifier]".as_slice(), + token_type: Some(TokenType::TK_ID), + }, + ), + ( + b"?123".as_slice(), + Token { + value: b"123".as_slice(), // '?' is not included in the value + token_type: Some(TokenType::TK_VARIABLE), + }, + ), + ( + b"$var_name".as_slice(), + Token { + value: b"$var_name".as_slice(), + token_type: Some(TokenType::TK_VARIABLE), + }, + ), + ( + b"@param".as_slice(), + Token { + value: b"@param".as_slice(), + token_type: Some(TokenType::TK_VARIABLE), + }, + ), + ( + b"#comment".as_slice(), + Token { + value: b"#comment".as_slice(), + token_type: Some(TokenType::TK_VARIABLE), + }, + ), + ( + b":named_param".as_slice(), + Token { + value: b":named_param".as_slice(), + token_type: Some(TokenType::TK_VARIABLE), + }, + ), + ( + b"x'1234567890abcdef'".as_slice(), + Token { + value: b"1234567890abcdef".as_slice(), // 'x' is not included in the value + token_type: Some(TokenType::TK_BLOB), + }, + ), + ( + b"X'1234567890abcdef'".as_slice(), + Token { + value: b"1234567890abcdef".as_slice(), // 'X' is not included in the value + token_type: Some(TokenType::TK_BLOB), + }, + ), + ( + b"x''".as_slice(), + Token { + value: b"".as_slice(), // 'x' is not included in the value + token_type: Some(TokenType::TK_BLOB), + }, + ), + ( + b"X''".as_slice(), + Token { + value: b"".as_slice(), // 'X' is not included in the value + token_type: Some(TokenType::TK_BLOB), + }, + ), + ( + b"wHeRe".as_slice(), + Token { + value: b"wHeRe".as_slice(), // 'X' is not included in the value + token_type: Some(TokenType::TK_WHERE), + }, + ), + ( + b"wHeRe123".as_slice(), + Token { + value: b"wHeRe123".as_slice(), // 'X' is not included in the value + token_type: Some(TokenType::TK_ID), + }, + ), + ( + b"wHeRe_123".as_slice(), + Token { + value: b"wHeRe_123".as_slice(), // 'X' is not included in the value + token_type: Some(TokenType::TK_ID), + }, + ), + ]; + + for (input, expected) in test_cases { + let mut lexer = Lexer::new(input); + let token = lexer.next().unwrap().unwrap(); + let expect_value = unsafe { String::from_utf8_unchecked(expected.value.to_vec()) }; + let got_value = unsafe { String::from_utf8_unchecked(token.value.to_vec()) }; + println!("Input: {input:?}, Expected: {expect_value:?}, Got: {got_value:?}"); + assert_eq!(got_value, expect_value); + assert_eq!(token.token_type, expected.token_type); + } + } + + #[test] + fn test_keyword_token() { + let values = HashMap::from([ + ("ABORT", TokenType::TK_ABORT), + ("ACTION", TokenType::TK_ACTION), + ("ADD", TokenType::TK_ADD), + ("AFTER", TokenType::TK_AFTER), + ("ALL", TokenType::TK_ALL), + ("ALTER", TokenType::TK_ALTER), + ("ALWAYS", TokenType::TK_ALWAYS), + ("ANALYZE", TokenType::TK_ANALYZE), + ("AND", TokenType::TK_AND), + ("AS", TokenType::TK_AS), + ("ASC", TokenType::TK_ASC), + ("ATTACH", TokenType::TK_ATTACH), + ("AUTOINCREMENT", TokenType::TK_AUTOINCR), + ("BEFORE", TokenType::TK_BEFORE), + ("BEGIN", TokenType::TK_BEGIN), + ("BETWEEN", TokenType::TK_BETWEEN), + ("BY", TokenType::TK_BY), + ("CASCADE", TokenType::TK_CASCADE), + ("CASE", TokenType::TK_CASE), + ("CAST", TokenType::TK_CAST), + ("CHECK", TokenType::TK_CHECK), + ("COLLATE", TokenType::TK_COLLATE), + ("COLUMN", TokenType::TK_COLUMNKW), + ("COMMIT", TokenType::TK_COMMIT), + ("CONFLICT", TokenType::TK_CONFLICT), + ("CONSTRAINT", TokenType::TK_CONSTRAINT), + ("CREATE", TokenType::TK_CREATE), + ("CROSS", TokenType::TK_JOIN_KW), + ("CURRENT", TokenType::TK_CURRENT), + ("CURRENT_DATE", TokenType::TK_CTIME_KW), + ("CURRENT_TIME", TokenType::TK_CTIME_KW), + ("CURRENT_TIMESTAMP", TokenType::TK_CTIME_KW), + ("DATABASE", TokenType::TK_DATABASE), + ("DEFAULT", TokenType::TK_DEFAULT), + ("DEFERRABLE", TokenType::TK_DEFERRABLE), + ("DEFERRED", TokenType::TK_DEFERRED), + ("DELETE", TokenType::TK_DELETE), + ("DESC", TokenType::TK_DESC), + ("DETACH", TokenType::TK_DETACH), + ("DISTINCT", TokenType::TK_DISTINCT), + ("DO", TokenType::TK_DO), + ("DROP", TokenType::TK_DROP), + ("EACH", TokenType::TK_EACH), + ("ELSE", TokenType::TK_ELSE), + ("END", TokenType::TK_END), + ("ESCAPE", TokenType::TK_ESCAPE), + ("EXCEPT", TokenType::TK_EXCEPT), + ("EXCLUDE", TokenType::TK_EXCLUDE), + ("EXCLUSIVE", TokenType::TK_EXCLUSIVE), + ("EXISTS", TokenType::TK_EXISTS), + ("EXPLAIN", TokenType::TK_EXPLAIN), + ("FAIL", TokenType::TK_FAIL), + ("FILTER", TokenType::TK_FILTER), + ("FIRST", TokenType::TK_FIRST), + ("FOLLOWING", TokenType::TK_FOLLOWING), + ("FOR", TokenType::TK_FOR), + ("FOREIGN", TokenType::TK_FOREIGN), + ("FROM", TokenType::TK_FROM), + ("FULL", TokenType::TK_JOIN_KW), + ("GENERATED", TokenType::TK_GENERATED), + ("GLOB", TokenType::TK_LIKE_KW), + ("GROUP", TokenType::TK_GROUP), + ("GROUPS", TokenType::TK_GROUPS), + ("HAVING", TokenType::TK_HAVING), + ("IF", TokenType::TK_IF), + ("IGNORE", TokenType::TK_IGNORE), + ("IMMEDIATE", TokenType::TK_IMMEDIATE), + ("IN", TokenType::TK_IN), + ("INDEX", TokenType::TK_INDEX), + ("INDEXED", TokenType::TK_INDEXED), + ("INITIALLY", TokenType::TK_INITIALLY), + ("INNER", TokenType::TK_JOIN_KW), + ("INSERT", TokenType::TK_INSERT), + ("INSTEAD", TokenType::TK_INSTEAD), + ("INTERSECT", TokenType::TK_INTERSECT), + ("INTO", TokenType::TK_INTO), + ("IS", TokenType::TK_IS), + ("ISNULL", TokenType::TK_ISNULL), + ("JOIN", TokenType::TK_JOIN), + ("KEY", TokenType::TK_KEY), + ("LAST", TokenType::TK_LAST), + ("LEFT", TokenType::TK_JOIN_KW), + ("LIKE", TokenType::TK_LIKE_KW), + ("LIMIT", TokenType::TK_LIMIT), + ("MATCH", TokenType::TK_MATCH), + ("MATERIALIZED", TokenType::TK_MATERIALIZED), + ("NATURAL", TokenType::TK_JOIN_KW), + ("NO", TokenType::TK_NO), + ("NOT", TokenType::TK_NOT), + ("NOTHING", TokenType::TK_NOTHING), + ("NOTNULL", TokenType::TK_NOTNULL), + ("NULL", TokenType::TK_NULL), + ("NULLS", TokenType::TK_NULLS), + ("OF", TokenType::TK_OF), + ("OFFSET", TokenType::TK_OFFSET), + ("ON", TokenType::TK_ON), + ("OR", TokenType::TK_OR), + ("ORDER", TokenType::TK_ORDER), + ("OTHERS", TokenType::TK_OTHERS), + ("OUTER", TokenType::TK_JOIN_KW), + ("OVER", TokenType::TK_OVER), + ("PARTITION", TokenType::TK_PARTITION), + ("PLAN", TokenType::TK_PLAN), + ("PRAGMA", TokenType::TK_PRAGMA), + ("PRECEDING", TokenType::TK_PRECEDING), + ("PRIMARY", TokenType::TK_PRIMARY), + ("QUERY", TokenType::TK_QUERY), + ("RAISE", TokenType::TK_RAISE), + ("RANGE", TokenType::TK_RANGE), + ("RECURSIVE", TokenType::TK_RECURSIVE), + ("REFERENCES", TokenType::TK_REFERENCES), + ("REGEXP", TokenType::TK_LIKE_KW), + ("REINDEX", TokenType::TK_REINDEX), + ("RELEASE", TokenType::TK_RELEASE), + ("RENAME", TokenType::TK_RENAME), + ("REPLACE", TokenType::TK_REPLACE), + ("RETURNING", TokenType::TK_RETURNING), + ("RESTRICT", TokenType::TK_RESTRICT), + ("RIGHT", TokenType::TK_JOIN_KW), + ("ROLLBACK", TokenType::TK_ROLLBACK), + ("ROW", TokenType::TK_ROW), + ("ROWS", TokenType::TK_ROWS), + ("SAVEPOINT", TokenType::TK_SAVEPOINT), + ("SELECT", TokenType::TK_SELECT), + ("SET", TokenType::TK_SET), + ("TABLE", TokenType::TK_TABLE), + ("TEMP", TokenType::TK_TEMP), + ("TEMPORARY", TokenType::TK_TEMP), + ("THEN", TokenType::TK_THEN), + ("TIES", TokenType::TK_TIES), + ("TO", TokenType::TK_TO), + ("TRANSACTION", TokenType::TK_TRANSACTION), + ("TRIGGER", TokenType::TK_TRIGGER), + ("UNBOUNDED", TokenType::TK_UNBOUNDED), + ("UNION", TokenType::TK_UNION), + ("UNIQUE", TokenType::TK_UNIQUE), + ("UPDATE", TokenType::TK_UPDATE), + ("USING", TokenType::TK_USING), + ("VACUUM", TokenType::TK_VACUUM), + ("VALUES", TokenType::TK_VALUES), + ("VIEW", TokenType::TK_VIEW), + ("VIRTUAL", TokenType::TK_VIRTUAL), + ("WHEN", TokenType::TK_WHEN), + ("WHERE", TokenType::TK_WHERE), + ("WINDOW", TokenType::TK_WINDOW), + ("WITH", TokenType::TK_WITH), + ("WITHOUT", TokenType::TK_WITHOUT), + ]); + + for (key, value) in &values { + assert!(keyword_token(key.as_bytes()).unwrap() == *value); + assert!( + keyword_token(key.as_bytes().to_ascii_lowercase().as_slice()).unwrap() == *value + ); + } + + assert!(keyword_token(b"").is_none()); + assert!(keyword_token(b"wrong").is_none()); + assert!(keyword_token(b"super wrong").is_none()); + assert!(keyword_token(b"super_wrong").is_none()); + assert!(keyword_token(b"aae26e78-3ba7-4627-8f8f-02623302495a").is_none()); + assert!(keyword_token("Crème Brulée".as_bytes()).is_none()); + assert!(keyword_token("fróm".as_bytes()).is_none()); + } + + #[test] + fn test_lexer_multi_tok() { + let test_cases = vec![ + ( + b" SELECT 1".as_slice(), + vec![ + Token { + value: b" ".as_slice(), + token_type: None, + }, + Token { + value: b"SELECT".as_slice(), + token_type: Some(TokenType::TK_SELECT), + }, + Token { + value: b" ".as_slice(), + token_type: None, + }, + Token { + value: b"1".as_slice(), + token_type: Some(TokenType::TK_INTEGER), + }, + ], + ), + ( + b"INSERT INTO users VALUES (1,2,3)".as_slice(), + vec![ + Token { + value: b"INSERT".as_slice(), + token_type: Some(TokenType::TK_INSERT), + }, + Token { + value: b" ".as_slice(), + token_type: None, + }, + Token { + value: b"INTO".as_slice(), + token_type: Some(TokenType::TK_INTO), + }, + Token { + value: b" ".as_slice(), + token_type: None, + }, + Token { + value: b"users".as_slice(), + token_type: Some(TokenType::TK_ID), + }, + Token { + value: b" ".as_slice(), + token_type: None, + }, + Token { + value: b"VALUES".as_slice(), + token_type: Some(TokenType::TK_VALUES), + }, + Token { + value: b" ".as_slice(), + token_type: None, + }, + Token { + value: b"(".as_slice(), + token_type: Some(TokenType::TK_LP), + }, + Token { + value: b"1".as_slice(), + token_type: Some(TokenType::TK_INTEGER), + }, + Token { + value: b",".as_slice(), + token_type: Some(TokenType::TK_COMMA), + }, + Token { + value: b"2".as_slice(), + token_type: Some(TokenType::TK_INTEGER), + }, + Token { + value: b",".as_slice(), + token_type: Some(TokenType::TK_COMMA), + }, + Token { + value: b"3".as_slice(), + token_type: Some(TokenType::TK_INTEGER), + }, + Token { + value: b")".as_slice(), + token_type: Some(TokenType::TK_RP), + }, + ], + ), + ]; + + for (input, expected_tokens) in test_cases { + let lexer = Lexer::new(input); + let mut tokens = Vec::new(); + + for token in lexer { + tokens.push(token.unwrap()); + } + + assert_eq!(tokens.len(), expected_tokens.len()); + + for (i, token) in tokens.iter().enumerate() { + let expect_value = + unsafe { String::from_utf8_unchecked(expected_tokens[i].value.to_vec()) }; + let got_value = unsafe { String::from_utf8_unchecked(token.value.to_vec()) }; + assert_eq!(got_value, expect_value); + assert_eq!(token.token_type, expected_tokens[i].token_type); + } + } + } +} diff --git a/parser/src/lib.rs b/parser/src/lib.rs new file mode 100644 index 000000000..bc1e427b5 --- /dev/null +++ b/parser/src/lib.rs @@ -0,0 +1,5 @@ +pub mod ast; +pub mod error; +pub mod lexer; +pub mod parser; +pub mod token; diff --git a/parser/src/parser.rs b/parser/src/parser.rs new file mode 100644 index 000000000..56c9a3f23 --- /dev/null +++ b/parser/src/parser.rs @@ -0,0 +1,11230 @@ +use crate::ast::{ + AlterTableBody, As, Cmd, ColumnConstraint, ColumnDefinition, CommonTableExpr, CompoundOperator, + CompoundSelect, CreateTableBody, DeferSubclause, Distinctness, Expr, ForeignKeyClause, + FrameBound, FrameClause, FrameExclude, FrameMode, FromClause, FunctionTail, GroupBy, Indexed, + IndexedColumn, InitDeferredPred, InsertBody, JoinConstraint, JoinOperator, JoinType, + JoinedSelectTable, LikeOperator, Limit, Literal, Materialized, Name, NamedColumnConstraint, + NamedTableConstraint, NullsOrder, OneSelect, Operator, Over, PragmaBody, PragmaValue, + QualifiedName, RefAct, RefArg, ResolveType, ResultColumn, Select, SelectBody, SelectTable, Set, + SortOrder, SortedColumn, Stmt, TableConstraint, TableOptions, TransactionType, TriggerCmd, + TriggerEvent, TriggerTime, Type, TypeSize, UnaryOperator, Upsert, UpsertDo, UpsertIndex, + Window, WindowDef, With, +}; +use crate::error::Error; +use crate::lexer::{Lexer, Token}; +use crate::token::TokenType::{self, *}; + +macro_rules! peek_expect { + ( $parser:expr, $( $x:ident ),* $(,)?) => { + { + let token = $parser.peek_no_eof()?; + match token.token_type.unwrap() { + $($x => token,)* + tt => { + // handle fallback TK_ID + match (TK_ID, tt.fallback_id_if_ok()) { + $(($x, TK_ID) => token,)* + _ => { + return Err(Error::ParseUnexpectedToken { + parsed_offset: ($parser.lexer.offset, 1).into(), + expected: &[ + $($x,)* + ], + got: token.token_type.unwrap(), + }) + } + } + } + } + } + }; +} + +macro_rules! eat_assert { + ( $parser:expr, $( $x:ident ),* $(,)?) => { + { + let token = $parser.eat().unwrap().unwrap(); + + #[cfg(debug_assertions)] + match token.token_type.unwrap() { + $($x => token,)* + tt => { + // handle fallback TK_ID + match (TK_ID, tt.fallback_id_if_ok()) { + $(($x, TK_ID) => token,)* + _ => { + panic!( + "Expected token {:?}, got {:?}", + &[ $($x,)* ], + tt + ); + } + } + } + } + + #[cfg(not(debug_assertions))] + token // in release mode, we assume the caller has checked the token type + } + }; +} + +macro_rules! eat_expect { + ( $parser:expr, $( $x:ident ),* $(,)?) => { + { + peek_expect!($parser, $( $x ),*); + eat_assert!($parser, $( $x ),*) + } + }; +} + +#[inline(always)] +fn from_bytes_as_str(bytes: &[u8]) -> &str { + unsafe { str::from_utf8_unchecked(bytes) } +} + +#[inline(always)] +fn from_bytes(bytes: &[u8]) -> String { + unsafe { str::from_utf8_unchecked(bytes).to_owned() } +} + +#[inline] +fn join_type_from_bytes(s: &[u8]) -> Result { + if b"CROSS".eq_ignore_ascii_case(s) { + Ok(JoinType::INNER | JoinType::CROSS) + } else if b"FULL".eq_ignore_ascii_case(s) { + Ok(JoinType::LEFT | JoinType::RIGHT | JoinType::OUTER) + } else if b"INNER".eq_ignore_ascii_case(s) { + Ok(JoinType::INNER) + } else if b"LEFT".eq_ignore_ascii_case(s) { + Ok(JoinType::LEFT | JoinType::OUTER) + } else if b"NATURAL".eq_ignore_ascii_case(s) { + Ok(JoinType::NATURAL) + } else if b"RIGHT".eq_ignore_ascii_case(s) { + Ok(JoinType::RIGHT | JoinType::OUTER) + } else if b"OUTER".eq_ignore_ascii_case(s) { + Ok(JoinType::OUTER) + } else { + Err(Error::Custom(format!( + "unsupported JOIN type: {:?}", + str::from_utf8(s) + ))) + } +} + +#[inline] +fn new_join_type(n0: &[u8], n1: Option<&[u8]>, n2: Option<&[u8]>) -> Result { + let mut jt = join_type_from_bytes(n0)?; + + if let Some(n1) = n1 { + jt |= join_type_from_bytes(n1)?; + } + + if let Some(n2) = n2 { + jt |= join_type_from_bytes(n2)?; + } + + if (jt & (JoinType::INNER | JoinType::OUTER)) == (JoinType::INNER | JoinType::OUTER) + || (jt & (JoinType::OUTER | JoinType::LEFT | JoinType::RIGHT)) == JoinType::OUTER + { + return Err(Error::Custom(format!( + "unsupported JOIN type: {:?} {:?} {:?}", + from_bytes_as_str(n0), + from_bytes_as_str(n1.unwrap_or(&[])), + from_bytes_as_str(n2.unwrap_or(&[])), + ))); + } + + Ok(jt) +} + +pub struct Parser<'a> { + lexer: Lexer<'a>, + + /// The current token being processed + current_token: Token<'a>, + peekable: bool, +} + +impl<'a> Iterator for Parser<'a> { + type Item = Result; + + #[inline] + fn next(&mut self) -> Option { + match self.mark(|p| p.next_cmd()) { + Ok(None) => None, // EOF + Ok(Some(cmd)) => Some(Ok(cmd)), + Err(err) => Some(Err(err)), + } + } +} + +impl<'a> Parser<'a> { + #[inline(always)] + pub fn new(input: &'a [u8]) -> Self { + Self { + lexer: Lexer::new(input), + peekable: false, + current_token: Token { + value: b"", + token_type: None, + }, + } + } + + // entrypoint of parsing + fn next_cmd(&mut self) -> Result, Error> { + // consumes prefix SEMI + while let Some(token) = self.peek()? { + if token.token_type == Some(TK_SEMI) { + eat_assert!(self, TK_SEMI); + } else { + break; + } + } + + let result = match self.peek()? { + None => None, // EOF + Some(token) => match token.token_type.unwrap() { + TK_EXPLAIN => { + eat_assert!(self, TK_EXPLAIN); + + let mut is_query_plan = false; + if self.peek_no_eof()?.token_type == Some(TK_QUERY) { + eat_assert!(self, TK_QUERY); + eat_expect!(self, TK_PLAN); + is_query_plan = true; + } + + let stmt = self.parse_stmt()?; + if is_query_plan { + Some(Cmd::ExplainQueryPlan(stmt)) + } else { + Some(Cmd::Explain(stmt)) + } + } + _ => { + let stmt = self.parse_stmt()?; + Some(Cmd::Stmt(stmt)) + } + }, + }; + + let mut found_semi = false; + loop { + match self.peek()? { + None => break, + Some(token) if token.token_type == Some(TK_SEMI) => { + found_semi = true; + eat_assert!(self, TK_SEMI); + } + Some(token) => { + if !found_semi { + return Err(Error::ParseUnexpectedToken { + parsed_offset: (self.lexer.offset, 1).into(), + expected: &[TK_SEMI], + got: token.token_type.unwrap(), + }); + } + + break; + } + } + } + + Ok(result) + } + + #[inline(always)] + fn consume_lexer_without_whitespaces_or_comments( + &mut self, + ) -> Option, Error>> { + debug_assert!(!self.peekable); + loop { + let tok = self.lexer.next(); + if let Some(Ok(ref token)) = tok { + if token.token_type.is_none() { + continue; // white space or comment + } + } + + return tok; + } + } + + fn next_token(&mut self) -> Result>, Error> { + debug_assert!(!self.peekable); + let mut next = self.consume_lexer_without_whitespaces_or_comments(); + + fn get_token(tt: TokenType) -> TokenType { + match tt { + TK_ID | TK_STRING | TK_JOIN_KW | TK_UNION | TK_EXCEPT | TK_INTERSECT + | TK_GENERATED | TK_WITHOUT | TK_COLUMNKW | TK_WINDOW | TK_FILTER | TK_OVER => { + TK_ID + } + _ => tt.fallback_id_if_ok(), + } + } + + if let Some(Ok(ref mut tok)) = next { + /* + ** The following three functions are called immediately after the tokenizer + ** reads the keywords WINDOW, OVER and FILTER, respectively, to determine + ** whether the token should be treated as a keyword or an SQL identifier. + ** This cannot be handled by the usual lemon %fallback method, due to + ** the ambiguity in some constructions. e.g. + ** + ** SELECT sum(x) OVER ... + ** + ** In the above, "OVER" might be a keyword, or it might be an alias for the + ** sum(x) expression. If a "%fallback ID OVER" directive were added to + ** grammar, then SQLite would always treat "OVER" as an alias, making it + ** impossible to call a window-function without a FILTER clause. + ** + ** WINDOW is treated as a keyword if: + ** + ** * the following token is an identifier, or a keyword that can fallback + ** to being an identifier, and + ** * the token after than one is TK_AS. + ** + ** OVER is a keyword if: + ** + ** * the previous token was TK_RP, and + ** * the next token is either TK_LP or an identifier. + ** + ** FILTER is a keyword if: + ** + ** * the previous token was TK_RP, and + ** * the next token is TK_LP. + ** + ** UNION is a keyword if: + ** + ** * the next token is TK_ALL|TK_SELECT|TK_VALUES. + ** + ** EXCEPT is a keyword if: + ** + ** * the next token is TK_SELECT|TK_VALUES. + ** + ** INTERSECT is a keyword if: + ** + ** * the next token is TK_SELECT|TK_VALUES. + ** + ** COLUMNKW is a keyword if: + ** + ** * the previous token is TK_ADD|TK_RENAME|TK_DROP. + ** + ** GENERATED is a keyword if: + ** + ** * the next token is TK_ALWAYS. + ** * the token after than one is TK_AS. + ** + ** WITHOUT is a keyword if: + ** + ** * the previous token is TK_RP|TK_COMMA. + ** * the next token is TK_ID. + */ + match tok.token_type.unwrap() { + TK_WINDOW => { + let can_be_window = self.try_parse(|p| { + match p.consume_lexer_without_whitespaces_or_comments() { + None => return Ok(false), + Some(tok) => match get_token(tok?.token_type.unwrap()) { + TK_ID => {} + _ => return Ok(false), + }, + } + + match p.consume_lexer_without_whitespaces_or_comments() { + None => Ok(false), + Some(tok) => match tok?.token_type.unwrap() { + TK_AS => Ok(true), + _ => Ok(false), + }, + } + })?; + + if !can_be_window { + tok.token_type = Some(TK_ID); + } + } + TK_OVER => { + let prev_tt = self.current_token.token_type.unwrap_or(TK_EOF); + let can_be_over = { + if prev_tt == TK_RP { + self.try_parse(|p| { + match p.consume_lexer_without_whitespaces_or_comments() { + None => Ok(false), + Some(tok) => match get_token(tok?.token_type.unwrap()) { + TK_LP | TK_ID => Ok(true), + _ => Ok(false), + }, + } + })? + } else { + false + } + }; + + if !can_be_over { + tok.token_type = Some(TK_ID); + } + } + TK_FILTER => { + let prev_tt = self.current_token.token_type.unwrap_or(TK_EOF); + let can_be_filter = { + if prev_tt == TK_RP { + self.try_parse(|p| { + match p.consume_lexer_without_whitespaces_or_comments() { + None => Ok(false), + Some(tok) => match tok?.token_type.unwrap() { + TK_LP => Ok(true), + _ => Ok(false), + }, + } + })? + } else { + false + } + }; + + if !can_be_filter { + tok.token_type = Some(TK_ID); + } + } + TK_UNION => { + let can_be_union = self.try_parse(|p| { + match p.consume_lexer_without_whitespaces_or_comments() { + None => Ok(false), + Some(tok) => match tok?.token_type.unwrap() { + TK_ALL | TK_SELECT | TK_VALUES => Ok(true), + _ => Ok(false), + }, + } + })?; + + if !can_be_union { + tok.token_type = Some(TK_ID); + } + } + TK_EXCEPT | TK_INTERSECT => { + let can_be_except = self.try_parse(|p| { + match p.consume_lexer_without_whitespaces_or_comments() { + None => Ok(false), + Some(tok) => match tok?.token_type.unwrap() { + TK_SELECT | TK_VALUES => Ok(true), + _ => Ok(false), + }, + } + })?; + + if !can_be_except { + tok.token_type = Some(TK_ID); + } + } + TK_COLUMNKW => { + let prev_tt = self.current_token.token_type.unwrap_or(TK_EOF); + let can_be_columnkw = matches!(prev_tt, TK_ADD | TK_RENAME | TK_DROP); + + if !can_be_columnkw { + tok.token_type = Some(TK_ID); + } + } + TK_GENERATED => { + let can_be_generated = self.try_parse(|p| { + match p.consume_lexer_without_whitespaces_or_comments() { + None => return Ok(false), + Some(tok) => match tok?.token_type.unwrap() { + TK_ALWAYS => {} + _ => return Ok(false), + }, + } + + match p.consume_lexer_without_whitespaces_or_comments() { + None => Ok(false), + Some(tok) => match tok?.token_type.unwrap() { + TK_AS => Ok(true), + _ => Ok(false), + }, + } + })?; + + if !can_be_generated { + tok.token_type = Some(TK_ID); + } + } + TK_WITHOUT => { + let prev_tt = self.current_token.token_type.unwrap_or(TK_EOF); + let can_be_without = match prev_tt { + TK_RP | TK_COMMA => self.try_parse(|p| { + match p.consume_lexer_without_whitespaces_or_comments() { + None => Ok(false), + Some(tok) => match get_token(tok?.token_type.unwrap()) { + TK_ID => Ok(true), + _ => Ok(false), + }, + } + })?, + _ => false, + }; + + if !can_be_without { + tok.token_type = Some(TK_ID); + } + } + _ => {} + } + } + + match next { + None => Ok(None), // EOF + Some(Ok(tok)) => { + self.current_token = tok.clone(); + self.peekable = true; + Ok(Some(tok)) + } + Some(Err(err)) => Err(err), + } + } + + #[inline] + fn mark(&mut self, exc: F) -> Result + where + F: FnOnce(&mut Self) -> Result, + { + let old_peekable = self.peekable; + let old_current_token = self.current_token.clone(); + let start_offset = self.lexer.offset; + let result = exc(self); + if result.is_err() { + self.peekable = old_peekable; + self.current_token = old_current_token; + self.lexer.offset = start_offset; + } + result + } + + #[inline] + fn try_parse(&mut self, exc: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + debug_assert!(!self.peekable); + let start_offset = self.lexer.offset; + let result = exc(self); + self.peekable = false; + self.lexer.offset = start_offset; + result + } + + /// Get the next token from the lexer + #[inline] + fn eat(&mut self) -> Result>, Error> { + let result = self.peek()?; + self.peekable = false; // Clear the peek mark after consuming + Ok(result) + } + + /// Peek at the next token without consuming it + #[inline] + fn peek(&mut self) -> Result>, Error> { + if self.peekable { + return Ok(Some(self.current_token.clone())); + } + + self.next_token() + } + + #[inline] + fn eat_no_eof(&mut self) -> Result, Error> { + match self.eat()? { + None => Err(Error::ParseUnexpectedEOF), + Some(token) => Ok(token), + } + } + + #[inline] + fn peek_no_eof(&mut self) -> Result, Error> { + match self.peek()? { + None => Err(Error::ParseUnexpectedEOF), + Some(token) => Ok(token), + } + } + + fn parse_stmt(&mut self) -> Result { + let tok = peek_expect!( + self, + TK_BEGIN, + TK_COMMIT, + TK_END, + TK_ROLLBACK, + TK_SAVEPOINT, + TK_RELEASE, + TK_CREATE, + TK_SELECT, + TK_VALUES, + TK_WITH, + TK_ANALYZE, + TK_ATTACH, + TK_DETACH, + TK_PRAGMA, + TK_VACUUM, + TK_ALTER, + TK_DELETE, + TK_DROP, + TK_INSERT, + TK_REPLACE, + TK_UPDATE, + TK_REINDEX + ); + + match tok.token_type.unwrap() { + TK_BEGIN => self.parse_begin(), + TK_COMMIT | TK_END => self.parse_commit(), + TK_ROLLBACK => self.parse_rollback(), + TK_SAVEPOINT => self.parse_savepoint(), + TK_RELEASE => self.parse_release(), + TK_CREATE => self.parse_create_stmt(), + TK_SELECT | TK_VALUES => Ok(Stmt::Select(self.parse_select()?)), + TK_WITH => self.parse_with_stmt(), + TK_ANALYZE => self.parse_analyze(), + TK_ATTACH => self.parse_attach(), + TK_DETACH => self.parse_detach(), + TK_PRAGMA => self.parse_pragma(), + TK_VACUUM => self.parse_vacuum(), + TK_ALTER => self.parse_alter(), + TK_DELETE => self.parse_delete(), + TK_DROP => self.parse_drop_stmt(), + TK_INSERT | TK_REPLACE => self.parse_insert(), + TK_UPDATE => self.parse_update(), + TK_REINDEX => self.parse_reindex(), + _ => unreachable!(), + } + } + + fn parse_nm(&mut self) -> Result { + let tok = eat_expect!(self, TK_ID, TK_STRING, TK_INDEXED, TK_JOIN_KW); + + let first_char = tok.value[0]; // no need to check empty + match first_char { + b'[' | b'\'' | b'`' | b'"' => Ok(Name::Quoted(from_bytes(tok.value))), + _ => Ok(Name::Ident(from_bytes(tok.value))), + } + } + + fn parse_transopt(&mut self) -> Result, Error> { + match self.peek()? { + None => Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_TRANSACTION => { + eat_assert!(self, TK_TRANSACTION); + match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_ID | TK_STRING | TK_INDEXED | TK_JOIN_KW => { + Ok(Some(self.parse_nm()?)) + } + _ => Ok(None), + }, + _ => Ok(None), + } + } + _ => Ok(None), + }, + } + } + + fn parse_begin(&mut self) -> Result { + eat_assert!(self, TK_BEGIN); + + let transtype = match self.peek()? { + None => None, + Some(tok) => match tok.token_type.unwrap() { + TK_DEFERRED => { + eat_assert!(self, TK_DEFERRED); + Some(TransactionType::Deferred) + } + TK_IMMEDIATE => { + eat_assert!(self, TK_IMMEDIATE); + Some(TransactionType::Immediate) + } + TK_EXCLUSIVE => { + eat_assert!(self, TK_EXCLUSIVE); + Some(TransactionType::Exclusive) + } + _ => None, + }, + }; + + Ok(Stmt::Begin { + typ: transtype, + name: self.parse_transopt()?, + }) + } + + fn parse_commit(&mut self) -> Result { + eat_assert!(self, TK_COMMIT, TK_END); + Ok(Stmt::Commit { + name: self.parse_transopt()?, + }) + } + + fn parse_rollback(&mut self) -> Result { + eat_assert!(self, TK_ROLLBACK); + + let tx_name = self.parse_transopt()?; + + let savepoint_name = match self.peek()? { + None => None, + Some(tok) => { + if tok.token_type == Some(TK_TO) { + eat_assert!(self, TK_TO); + + if self.peek_no_eof()?.token_type == Some(TK_SAVEPOINT) { + eat_assert!(self, TK_SAVEPOINT); + } + + Some(self.parse_nm()?) + } else { + None + } + } + }; + + Ok(Stmt::Rollback { + tx_name, + savepoint_name, + }) + } + + fn parse_savepoint(&mut self) -> Result { + eat_assert!(self, TK_SAVEPOINT); + Ok(Stmt::Savepoint { + name: self.parse_nm()?, + }) + } + + fn parse_release(&mut self) -> Result { + eat_assert!(self, TK_RELEASE); + + if self.peek_no_eof()?.token_type == Some(TK_SAVEPOINT) { + eat_assert!(self, TK_SAVEPOINT); + } + + Ok(Stmt::Release { + name: self.parse_nm()?, + }) + } + + fn parse_create_view(&mut self, temporary: bool) -> Result { + eat_assert!(self, TK_VIEW); + let if_not_exists = self.parse_if_not_exists()?; + let view_name = self.parse_fullname(false)?; + let columns = self.parse_eid_list()?; + eat_expect!(self, TK_AS); + let select = self.parse_select()?; + Ok(Stmt::CreateView { + temporary, + if_not_exists, + view_name, + columns, + select, + }) + } + + fn parse_vtab_arg(&mut self) -> Result { + let tok = self.peek_no_eof()?; + + // minus len() because lexer already consumed the token + let start_idx = self.lexer.offset - tok.value.len(); + + loop { + match self.peek_no_eof()?.token_type.unwrap() { + TK_LP => { + let mut lp_count: usize = 0; + loop { + let tok = self.eat_no_eof()?; + match tok.token_type.unwrap() { + TK_LP => { + lp_count += 1; + } + TK_RP => { + lp_count -= 1; // FIXME: no need to check underflow + if lp_count == 0 { + break; + } + } + _ => {} + } + } + } + TK_COMMA | TK_RP => break, + _ => { + self.eat_no_eof()?; + } + } + } + + // minus 1 because lexer already consumed TK_COMMA or TK_RP + let end_idx = self.lexer.offset - 1; + if start_idx == end_idx { + return Err(Error::Custom("unexpected COMMA in vtab args".to_owned())); + } + + Ok(from_bytes(&self.lexer.input[start_idx..end_idx])) + } + + fn parse_create_virtual(&mut self) -> Result { + eat_assert!(self, TK_VIRTUAL); + eat_expect!(self, TK_TABLE); + let if_not_exists = self.parse_if_not_exists()?; + let tbl_name = self.parse_fullname(false)?; + eat_expect!(self, TK_USING); + let module_name = self.parse_nm()?; + let args = match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_LP => { + eat_assert!(self, TK_LP); + let mut result = vec![]; + loop { + if self.peek_no_eof()?.token_type == Some(TK_RP) { + break; // handle empty args case + } + + result.push(self.parse_vtab_arg()?); + let tok = peek_expect!(self, TK_COMMA, TK_RP); + match tok.token_type.unwrap() { + TK_COMMA => { + eat_assert!(self, TK_COMMA); + } + TK_RP => break, + _ => unreachable!(), + } + } + + eat_assert!(self, TK_RP); // already checks in loop + result + } + _ => vec![], + }, + _ => vec![], + }; + + Ok(Stmt::CreateVirtualTable { + if_not_exists, + tbl_name, + module_name, + args, + }) + } + + fn parse_create_stmt(&mut self) -> Result { + eat_assert!(self, TK_CREATE); + let mut first_tok = peek_expect!( + self, TK_TEMP, TK_TABLE, TK_VIRTUAL, TK_VIEW, TK_INDEX, TK_UNIQUE, TK_TRIGGER + ); + let mut temp = false; + if first_tok.token_type == Some(TK_TEMP) { + eat_assert!(self, TK_TEMP); + temp = true; + first_tok = peek_expect!(self, TK_TABLE, TK_VIEW, TK_TRIGGER); + } + + match first_tok.token_type.unwrap() { + TK_TABLE => self.parse_create_table(temp), + TK_VIEW => self.parse_create_view(temp), + TK_TRIGGER => self.parse_create_trigger(temp), + TK_VIRTUAL => self.parse_create_virtual(), + TK_INDEX | TK_UNIQUE => self.parse_create_index(), + _ => unreachable!(), + } + } + + fn parse_with_stmt(&mut self) -> Result { + let with = self.parse_with()?; + debug_assert!(with.is_some()); + let first_tok = + peek_expect!(self, TK_SELECT, TK_VALUES, TK_UPDATE, TK_DELETE, TK_INSERT, TK_REPLACE,); + + match first_tok.token_type.unwrap() { + TK_SELECT | TK_VALUES => Ok(Stmt::Select(self.parse_select_without_cte(with)?)), + TK_UPDATE => self.parse_update_without_cte(with), + TK_DELETE => self.parse_delete_without_cte(with), + TK_INSERT | TK_REPLACE => self.parse_insert_without_cte(with), + _ => unreachable!(), + } + } + + fn parse_if_not_exists(&mut self) -> Result { + if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_IF) { + eat_assert!(self, TK_IF); + } else { + return Ok(false); + } + } else { + return Ok(false); + } + + eat_expect!(self, TK_NOT); + eat_expect!(self, TK_EXISTS); + Ok(true) + } + + fn parse_fullname(&mut self, allow_alias: bool) -> Result { + let first_name = self.parse_nm()?; + + let secone_name = if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_DOT) { + eat_assert!(self, TK_DOT); + Some(self.parse_nm()?) + } else { + None + } + } else { + None + }; + + let alias_name = if allow_alias { + if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_AS) { + eat_assert!(self, TK_AS); + Some(self.parse_nm()?) + } else { + None + } + } else { + None + } + } else { + None + }; + + if let Some(secone_name) = secone_name { + Ok(QualifiedName { + db_name: Some(first_name), + name: secone_name, + alias: alias_name, + }) + } else { + Ok(QualifiedName { + db_name: None, + name: first_name, + alias: alias_name, + }) + } + } + + fn parse_signed(&mut self) -> Result, Error> { + peek_expect!(self, TK_FLOAT, TK_INTEGER, TK_PLUS, TK_MINUS); + + let expr = self.parse_expr_operand()?; + match expr.as_ref() { + Expr::Unary(_, inner) => match inner.as_ref() { + Expr::Literal(Literal::Numeric(_)) => Ok(expr), + _ => Err(Error::Custom( + "Expected a numeric literal after unary operator".to_string(), + )), + }, + _ => Ok(expr), + } + } + + fn parse_type(&mut self) -> Result, Error> { + let mut type_name = if let Some(tok) = self.peek()? { + match tok.token_type.unwrap().fallback_id_if_ok() { + TK_ID | TK_STRING => { + eat_assert!(self, TK_ID, TK_STRING); + from_bytes(tok.value) + } + _ => return Ok(None), + } + } else { + return Ok(None); + }; + + while let Some(tok) = self.peek()? { + match tok.token_type.unwrap().fallback_id_if_ok() { + TK_ID | TK_STRING => { + eat_assert!(self, TK_ID, TK_STRING); + type_name.push(' '); + type_name.push_str(from_bytes_as_str(tok.value)); + } + _ => break, + } + } + + let size = if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_LP) { + eat_assert!(self, TK_LP); + let first_size = self.parse_signed()?; + let tok = eat_expect!(self, TK_RP, TK_COMMA); + match tok.token_type.unwrap() { + TK_RP => Some(TypeSize::MaxSize(first_size)), + TK_COMMA => { + let second_size = self.parse_signed()?; + eat_expect!(self, TK_RP); + Some(TypeSize::TypeSize(first_size, second_size)) + } + _ => unreachable!(), + } + } else { + None + } + } else { + None + }; + + Ok(Some(Type { + name: type_name, + size, + })) + } + + /// SQLite understands these operators, listed in precedence1 order + /// (top to bottom / highest to lowest): + /// + /// Operators 2 + /// 11 -> ~ [expr] + [expr] - [expr] + /// 10 -> [expr] COLLATE (collation-name) 3 + /// 9 -> || -> ->> + /// 8 -> * / % + /// 7 -> + - + /// 6 -> &  | <<  >> + /// 5 -> [expr] ESCAPE [escape-character-expr] 4 + /// 4 -> < > <= >= + /// 3 -> = == <> != IS IS NOT + /// IS DISTINCT FROM IS NOT DISTINCT FROM + /// [expr] BETWEEN5 [expr] AND [expr] + /// IN5 MATCH5 LIKE5 REGEXP5 GLOB5 + /// [expr] ISNULL [expr] NOTNULL [expr] NOT NULL + /// 2 NOT [expr] + /// 1 -> AND + /// 0 -> OR + /// + /// this function detect precedence by peeking first token of operator + /// after parsing a operand (binary operator) + fn current_token_precedence(&mut self) -> Result, Error> { + let tok = self.peek()?; + if tok.is_none() { + return Ok(None); + } + + match tok.unwrap().token_type.unwrap() { + TK_OR => Ok(Some(0)), + TK_AND => Ok(Some(1)), + TK_NOT => Ok(Some(3)), // NOT is 3 because of binary operator + TK_EQ | TK_NE | TK_IS | TK_BETWEEN | TK_IN | TK_MATCH | TK_LIKE_KW | TK_ISNULL + | TK_NOTNULL => Ok(Some(3)), + TK_LT | TK_GT | TK_LE | TK_GE => Ok(Some(4)), + TK_ESCAPE => Ok(None), // ESCAPE will be consumed after parsing MATCH|LIKE_KW + TK_BITAND | TK_BITOR | TK_LSHIFT | TK_RSHIFT => Ok(Some(6)), + TK_PLUS | TK_MINUS => Ok(Some(7)), + TK_STAR | TK_SLASH | TK_REM => Ok(Some(8)), + TK_CONCAT | TK_PTR => Ok(Some(9)), + TK_COLLATE => Ok(Some(10)), + // no need 11 because its for unary operators + _ => Ok(None), + } + } + + fn parse_distinct(&mut self) -> Result, Error> { + match self.peek()? { + None => Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_DISTINCT => { + eat_assert!(self, TK_DISTINCT); + Ok(Some(Distinctness::Distinct)) + } + TK_ALL => { + eat_assert!(self, TK_ALL); + Ok(Some(Distinctness::All)) + } + _ => Ok(None), + }, + } + } + + fn parse_filter_clause(&mut self) -> Result>, Error> { + match self.peek()? { + None => return Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_FILTER => { + eat_assert!(self, TK_FILTER); + } + _ => return Ok(None), + }, + } + + eat_expect!(self, TK_LP); + eat_expect!(self, TK_WHERE); + let expr = self.parse_expr(0)?; + eat_expect!(self, TK_RP); + Ok(Some(expr)) + } + + fn parse_frame_opt(&mut self) -> Result, Error> { + let range_or_rows = match self.peek()? { + None => return Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_RANGE => { + eat_assert!(self, TK_RANGE); + FrameMode::Range + } + TK_ROWS => { + eat_assert!(self, TK_ROWS); + FrameMode::Rows + } + TK_GROUPS => { + eat_assert!(self, TK_GROUPS); + FrameMode::Groups + } + _ => return Ok(None), + }, + }; + + let has_end = match self.peek_no_eof()?.token_type.unwrap() { + TK_BETWEEN => { + eat_assert!(self, TK_BETWEEN); + true + } + _ => false, + }; + + let start = match self.peek_no_eof()?.token_type.unwrap() { + TK_UNBOUNDED => { + eat_assert!(self, TK_UNBOUNDED); + eat_expect!(self, TK_PRECEDING); + FrameBound::UnboundedPreceding + } + TK_CURRENT => { + eat_assert!(self, TK_CURRENT); + eat_expect!(self, TK_ROW); + FrameBound::CurrentRow + } + _ => { + let expr = self.parse_expr(0)?; + let tok = eat_expect!(self, TK_PRECEDING, TK_FOLLOWING); + match tok.token_type.unwrap() { + TK_PRECEDING => FrameBound::Preceding(expr), + TK_FOLLOWING => FrameBound::Following(expr), + _ => unreachable!(), + } + } + }; + + let end = if has_end { + eat_expect!(self, TK_AND); + + Some(match self.peek_no_eof()?.token_type.unwrap() { + TK_UNBOUNDED => { + eat_assert!(self, TK_UNBOUNDED); + eat_expect!(self, TK_FOLLOWING); + FrameBound::UnboundedFollowing + } + TK_CURRENT => { + eat_assert!(self, TK_CURRENT); + eat_expect!(self, TK_ROW); + FrameBound::CurrentRow + } + _ => { + let expr = self.parse_expr(0)?; + let tok = eat_expect!(self, TK_PRECEDING, TK_FOLLOWING); + match tok.token_type.unwrap() { + TK_PRECEDING => FrameBound::Preceding(expr), + TK_FOLLOWING => FrameBound::Following(expr), + _ => unreachable!(), + } + } + }) + } else { + None + }; + + let exclude = match self.peek()? { + None => None, + Some(tok) => match tok.token_type.unwrap() { + TK_EXCLUDE => { + eat_assert!(self, TK_EXCLUDE); + let tok = eat_expect!(self, TK_NO, TK_CURRENT, TK_GROUP, TK_TIES); + match tok.token_type.unwrap() { + TK_NO => { + eat_expect!(self, TK_OTHERS); + Some(FrameExclude::NoOthers) + } + TK_CURRENT => { + eat_expect!(self, TK_ROW); + Some(FrameExclude::CurrentRow) + } + TK_GROUP => Some(FrameExclude::Group), + TK_TIES => Some(FrameExclude::Ties), + _ => unreachable!(), + } + } + _ => None, + }, + }; + + Ok(Some(FrameClause { + mode: range_or_rows, + start, + end, + exclude, + })) + } + + fn parse_window(&mut self) -> Result { + let name = match self.peek()? { + None => None, + Some(tok) => match tok.token_type.unwrap() { + TK_PARTITION | TK_ORDER | TK_RANGE | TK_ROWS | TK_GROUPS => None, + tt => match tt.fallback_id_if_ok() { + TK_ID | TK_STRING | TK_INDEXED | TK_JOIN_KW => Some(self.parse_nm()?), + _ => None, + }, + }, + }; + + let partition_by = match self.peek()? { + Some(tok) if tok.token_type == Some(TK_PARTITION) => { + eat_assert!(self, TK_PARTITION); + eat_expect!(self, TK_BY); + self.parse_nexpr_list()? + } + _ => vec![], + }; + + let order_by = self.parse_order_by()?; + let frame_clause = self.parse_frame_opt()?; + Ok(Window { + base: name, + partition_by, + order_by, + frame_clause, + }) + } + + fn parse_over_clause(&mut self) -> Result, Error> { + match self.peek()? { + None => return Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_OVER => { + eat_assert!(self, TK_OVER); + } + _ => return Ok(None), + }, + } + + let tok = peek_expect!(self, TK_LP, TK_ID, TK_STRING, TK_INDEXED, TK_JOIN_KW); + match tok.token_type.unwrap() { + TK_LP => { + eat_assert!(self, TK_LP); + let window = self.parse_window()?; + eat_expect!(self, TK_RP); + Ok(Some(Over::Window(window))) + } + _ => Ok(Some(Over::Name(self.parse_nm()?))), + } + } + + fn parse_filter_over(&mut self) -> Result { + let filter_clause = self.parse_filter_clause()?; + let over_clause = self.parse_over_clause()?; + Ok(FunctionTail { + filter_clause, + over_clause, + }) + } + + fn parse_raise_type(&mut self) -> Result { + let tok = eat_expect!(self, TK_ROLLBACK, TK_ABORT, TK_FAIL); + + match tok.token_type.unwrap() { + TK_ROLLBACK => Ok(ResolveType::Rollback), + TK_ABORT => Ok(ResolveType::Abort), + TK_FAIL => Ok(ResolveType::Fail), + _ => unreachable!(), + } + } + + fn parse_expr_operand(&mut self) -> Result, Error> { + let tok = peek_expect!( + self, + TK_LP, + TK_CAST, + TK_CTIME_KW, + TK_RAISE, + TK_ID, + TK_STRING, + TK_INDEXED, + TK_JOIN_KW, + TK_NULL, + TK_BLOB, + TK_FLOAT, + TK_INTEGER, + TK_VARIABLE, + TK_NOT, + TK_BITNOT, + TK_PLUS, + TK_MINUS, + TK_EXISTS, + TK_CASE, + ); + + match tok.token_type.unwrap() { + TK_LP => { + eat_assert!(self, TK_LP); + match self.peek_no_eof()?.token_type.unwrap() { + TK_WITH | TK_SELECT | TK_VALUES => { + let select = self.parse_select()?; + eat_expect!(self, TK_RP); + Ok(Box::new(Expr::Subquery(select))) + } + _ => { + let exprs = self.parse_nexpr_list()?; + eat_expect!(self, TK_RP); + Ok(Box::new(Expr::Parenthesized(exprs))) + } + } + } + TK_NULL => { + eat_assert!(self, TK_NULL); + Ok(Box::new(Expr::Literal(Literal::Null))) + } + TK_BLOB => { + eat_assert!(self, TK_BLOB); + Ok(Box::new(Expr::Literal(Literal::Blob(from_bytes( + tok.value, + ))))) + } + TK_FLOAT => { + eat_assert!(self, TK_FLOAT); + Ok(Box::new(Expr::Literal(Literal::Numeric(from_bytes( + tok.value, + ))))) + } + TK_INTEGER => { + eat_assert!(self, TK_INTEGER); + Ok(Box::new(Expr::Literal(Literal::Numeric(from_bytes( + tok.value, + ))))) + } + TK_VARIABLE => { + eat_assert!(self, TK_VARIABLE); + Ok(Box::new(Expr::Variable(from_bytes(tok.value)))) + } + TK_CAST => { + eat_assert!(self, TK_CAST); + eat_expect!(self, TK_LP); + let expr = self.parse_expr(0)?; + eat_expect!(self, TK_AS); + let typ = self.parse_type()?; + eat_expect!(self, TK_RP); + Ok(Box::new(Expr::Cast { + expr, + type_name: typ, + })) + } + TK_CTIME_KW => { + let tok = eat_assert!(self, TK_CTIME_KW); + if b"CURRENT_DATE".eq_ignore_ascii_case(tok.value) { + Ok(Box::new(Expr::Literal(Literal::CurrentDate))) + } else if b"CURRENT_TIME".eq_ignore_ascii_case(tok.value) { + Ok(Box::new(Expr::Literal(Literal::CurrentTime))) + } else if b"CURRENT_TIMESTAMP".eq_ignore_ascii_case(tok.value) { + Ok(Box::new(Expr::Literal(Literal::CurrentTimestamp))) + } else { + unreachable!() + } + } + TK_NOT => { + eat_assert!(self, TK_NOT); + let expr = self.parse_expr(2)?; // NOT precedence is 2 + Ok(Box::new(Expr::Unary(UnaryOperator::Not, expr))) + } + TK_BITNOT => { + eat_assert!(self, TK_BITNOT); + let expr = self.parse_expr(11)?; // BITNOT precedence is 11 + Ok(Box::new(Expr::Unary(UnaryOperator::BitwiseNot, expr))) + } + TK_PLUS => { + eat_assert!(self, TK_PLUS); + let expr = self.parse_expr(11)?; // PLUS precedence is 11 + Ok(Box::new(Expr::Unary(UnaryOperator::Positive, expr))) + } + TK_MINUS => { + eat_assert!(self, TK_MINUS); + let expr = self.parse_expr(11)?; // MINUS precedence is 11 + Ok(Box::new(Expr::Unary(UnaryOperator::Negative, expr))) + } + TK_EXISTS => { + eat_assert!(self, TK_EXISTS); + eat_expect!(self, TK_LP); + let select = self.parse_select()?; + eat_expect!(self, TK_RP); + Ok(Box::new(Expr::Exists(select))) + } + TK_CASE => { + eat_assert!(self, TK_CASE); + let base = if self.peek_no_eof()?.token_type.unwrap() != TK_WHEN { + Some(self.parse_expr(0)?) + } else { + None + }; + + eat_expect!(self, TK_WHEN); + let first_when = self.parse_expr(0)?; + eat_expect!(self, TK_THEN); + let mut when_then_pairs = vec![(first_when, self.parse_expr(0)?)]; + + while let Some(tok) = self.peek()? { + if tok.token_type.unwrap() != TK_WHEN { + break; + } + + eat_assert!(self, TK_WHEN); + let when = self.parse_expr(0)?; + eat_expect!(self, TK_THEN); + let then = self.parse_expr(0)?; + when_then_pairs.push((when, then)); + } + + let else_expr = if let Some(ok) = self.peek()? { + if ok.token_type == Some(TK_ELSE) { + eat_assert!(self, TK_ELSE); + Some(self.parse_expr(0)?) + } else { + None + } + } else { + None + }; + + eat_expect!(self, TK_END); + Ok(Box::new(Expr::Case { + base, + when_then_pairs, + else_expr, + })) + } + TK_RAISE => { + eat_assert!(self, TK_RAISE); + eat_expect!(self, TK_LP); + + let resolve = match self.peek_no_eof()?.token_type.unwrap() { + TK_IGNORE => { + eat_assert!(self, TK_IGNORE); + ResolveType::Ignore + } + _ => self.parse_raise_type()?, + }; + + let expr = if resolve != ResolveType::Ignore { + eat_expect!(self, TK_COMMA); + Some(self.parse_expr(0)?) + } else { + None + }; + + eat_expect!(self, TK_RP); + Ok(Box::new(Expr::Raise(resolve, expr))) + } + _ => { + let can_be_lit_str = tok.token_type == Some(TK_STRING); + let name = self.parse_nm()?; + + let second_name = if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_DOT) { + eat_assert!(self, TK_DOT); + Some(self.parse_nm()?) + } else if tok.token_type == Some(TK_LP) { + if can_be_lit_str { + return Err(Error::ParseUnexpectedToken { + parsed_offset: (self.lexer.offset, 1).into(), + got: TK_STRING, + expected: &[TK_ID, TK_INDEXED, TK_JOIN_KW], + }); + } // can not be literal string in function name + + eat_assert!(self, TK_LP); + let tok = self.peek_no_eof()?; + match tok.token_type.unwrap() { + TK_STAR => { + eat_assert!(self, TK_STAR); + eat_expect!(self, TK_RP); + return Ok(Box::new(Expr::FunctionCallStar { + name, + filter_over: self.parse_filter_over()?, + })); + } + _ => { + let distinct = self.parse_distinct()?; + let exprs = self.parse_expr_list()?; + eat_expect!(self, TK_RP); + let order_by = self.parse_order_by()?; + let filter_over = self.parse_filter_over()?; + return Ok(Box::new(Expr::FunctionCall { + name, + distinctness: distinct, + args: exprs, + order_by, + filter_over, + })); + } + } + } else { + None + } + } else { + None + }; + + let third_name = if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_DOT) { + debug_assert!(second_name.is_some()); + eat_assert!(self, TK_DOT); + Some(self.parse_nm()?) + } else { + None + } + } else { + None + }; + + if let Some(second_name) = second_name { + if let Some(third_name) = third_name { + Ok(Box::new(Expr::DoublyQualified( + name, + second_name, + third_name, + ))) + } else { + Ok(Box::new(Expr::Qualified(name, second_name))) + } + } else if can_be_lit_str { + Ok(Box::new(Expr::Literal(match name { + Name::Quoted(s) => Literal::String(s), + Name::Ident(s) => Literal::String(s), + }))) + } else { + Ok(Box::new(Expr::Id(name))) + } + } + } + } + + #[allow(clippy::vec_box)] + fn parse_expr_list(&mut self) -> Result>, Error> { + let mut exprs = vec![]; + while let Some(tok) = self.peek()? { + match tok.token_type.unwrap().fallback_id_if_ok() { + TK_LP | TK_CAST | TK_ID | TK_STRING | TK_INDEXED | TK_JOIN_KW | TK_NULL + | TK_BLOB | TK_FLOAT | TK_INTEGER | TK_VARIABLE | TK_CTIME_KW | TK_NOT + | TK_BITNOT | TK_PLUS | TK_MINUS | TK_EXISTS | TK_CASE => {} + _ => break, + } + + exprs.push(self.parse_expr(0)?); + match self.peek_no_eof()?.token_type.unwrap() { + TK_COMMA => { + eat_assert!(self, TK_COMMA); + } + _ => break, + } + } + + Ok(exprs) + } + + fn parse_expr(&mut self, precedence: u8) -> Result, Error> { + let mut result = self.parse_expr_operand()?; + + loop { + let pre = match self.current_token_precedence()? { + Some(pre) if pre < precedence => break, + None => break, // no more ops + Some(pre) => pre, + }; + + let mut tok = self.peek_no_eof()?; + let mut not = false; + if tok.token_type.unwrap() == TK_NOT { + eat_assert!(self, TK_NOT); + tok = peek_expect!( + self, TK_BETWEEN, TK_IN, TK_MATCH, TK_LIKE_KW, TK_NULL, // `NOT NULL` + ); + not = true; + } + + result = match tok.token_type.unwrap() { + TK_NULL => { + // special case `NOT NULL` + debug_assert!(not); // FIXME: not always true because of current_token_precedence + eat_assert!(self, TK_NULL); + Box::new(Expr::NotNull(result)) + } + TK_OR => { + eat_assert!(self, TK_OR); + Box::new(Expr::Binary(result, Operator::Or, self.parse_expr(pre)?)) + } + TK_AND => { + eat_assert!(self, TK_AND); + Box::new(Expr::Binary(result, Operator::And, self.parse_expr(pre)?)) + } + TK_EQ => { + eat_assert!(self, TK_EQ); + Box::new(Expr::Binary( + result, + Operator::Equals, + self.parse_expr(pre)?, + )) + } + TK_NE => { + eat_assert!(self, TK_NE); + Box::new(Expr::Binary( + result, + Operator::NotEquals, + self.parse_expr(pre)?, + )) + } + TK_IS => { + eat_assert!(self, TK_IS); + + let not = match self.peek_no_eof()?.token_type.unwrap() { + TK_NOT => { + eat_assert!(self, TK_NOT); + true + } + _ => false, + }; + + let op = match self.peek_no_eof()?.token_type.unwrap() { + TK_DISTINCT => { + eat_assert!(self, TK_DISTINCT); + eat_expect!(self, TK_FROM); + if not { + Operator::Is + } else { + Operator::IsNot + } + } + _ => { + if not { + Operator::IsNot + } else { + Operator::Is + } + } + }; + + Box::new(Expr::Binary(result, op, self.parse_expr(pre)?)) + } + TK_BETWEEN => { + eat_assert!(self, TK_BETWEEN); + let start = self.parse_expr(pre)?; + eat_expect!(self, TK_AND); + let end = self.parse_expr(pre)?; + Box::new(Expr::Between { + lhs: result, + not, + start, + end, + }) + } + TK_IN => { + eat_assert!(self, TK_IN); + let tok = self.peek_no_eof()?; + match tok.token_type.unwrap() { + TK_LP => { + eat_assert!(self, TK_LP); + let tok = self.peek_no_eof()?; + match tok.token_type.unwrap() { + TK_SELECT | TK_WITH | TK_VALUES => { + let select = self.parse_select()?; + eat_expect!(self, TK_RP); + Box::new(Expr::InSelect { + lhs: result, + not, + rhs: select, + }) + } + _ => { + let exprs = self.parse_expr_list()?; + eat_expect!(self, TK_RP); + Box::new(Expr::InList { + lhs: result, + not, + rhs: exprs, + }) + } + } + } + _ => { + let name = self.parse_fullname(false)?; + let mut exprs = vec![]; + if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_LP) { + eat_assert!(self, TK_LP); + exprs = self.parse_expr_list()?; + eat_expect!(self, TK_RP); + } + } + + Box::new(Expr::InTable { + lhs: result, + not, + rhs: name, + args: exprs, + }) + } + } + } + TK_MATCH | TK_LIKE_KW => { + let tok = eat_assert!(self, TK_MATCH, TK_LIKE_KW); + let op = match tok.token_type.unwrap() { + TK_MATCH => LikeOperator::Match, + TK_LIKE_KW => { + if b"LIKE".eq_ignore_ascii_case(tok.value) { + LikeOperator::Like + } else if b"GLOB".eq_ignore_ascii_case(tok.value) { + LikeOperator::Glob + } else if b"REGEXP".eq_ignore_ascii_case(tok.value) { + LikeOperator::Regexp + } else { + unreachable!() + } + } + _ => unreachable!(), + }; + + let expr = self.parse_expr(pre)?; + let escape = if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_ESCAPE) { + eat_assert!(self, TK_ESCAPE); + Some(self.parse_expr(pre)?) + } else { + None + } + } else { + None + }; + + Box::new(Expr::Like { + lhs: result, + not, + op, + rhs: expr, + escape, + }) + } + TK_ISNULL => { + eat_assert!(self, TK_ISNULL); + Box::new(Expr::IsNull(result)) + } + TK_NOTNULL => { + eat_assert!(self, TK_NOTNULL); + Box::new(Expr::NotNull(result)) + } + TK_LT => { + eat_assert!(self, TK_LT); + Box::new(Expr::Binary(result, Operator::Less, self.parse_expr(pre)?)) + } + TK_GT => { + eat_assert!(self, TK_GT); + Box::new(Expr::Binary( + result, + Operator::Greater, + self.parse_expr(pre)?, + )) + } + TK_LE => { + eat_assert!(self, TK_LE); + Box::new(Expr::Binary( + result, + Operator::LessEquals, + self.parse_expr(pre)?, + )) + } + TK_GE => { + eat_assert!(self, TK_GE); + Box::new(Expr::Binary( + result, + Operator::GreaterEquals, + self.parse_expr(pre)?, + )) + } + TK_ESCAPE => unreachable!(), + TK_BITAND => { + eat_assert!(self, TK_BITAND); + Box::new(Expr::Binary( + result, + Operator::BitwiseAnd, + self.parse_expr(pre)?, + )) + } + TK_BITOR => { + eat_assert!(self, TK_BITOR); + Box::new(Expr::Binary( + result, + Operator::BitwiseOr, + self.parse_expr(pre)?, + )) + } + TK_LSHIFT => { + eat_assert!(self, TK_LSHIFT); + Box::new(Expr::Binary( + result, + Operator::LeftShift, + self.parse_expr(pre)?, + )) + } + TK_RSHIFT => { + eat_assert!(self, TK_RSHIFT); + Box::new(Expr::Binary( + result, + Operator::RightShift, + self.parse_expr(pre)?, + )) + } + TK_PLUS => { + eat_assert!(self, TK_PLUS); + Box::new(Expr::Binary(result, Operator::Add, self.parse_expr(pre)?)) + } + TK_MINUS => { + eat_assert!(self, TK_MINUS); + Box::new(Expr::Binary( + result, + Operator::Subtract, + self.parse_expr(pre)?, + )) + } + TK_STAR => { + eat_assert!(self, TK_STAR); + Box::new(Expr::Binary( + result, + Operator::Multiply, + self.parse_expr(pre)?, + )) + } + TK_SLASH => { + eat_assert!(self, TK_SLASH); + Box::new(Expr::Binary( + result, + Operator::Divide, + self.parse_expr(pre)?, + )) + } + TK_REM => { + eat_assert!(self, TK_REM); + Box::new(Expr::Binary( + result, + Operator::Modulus, + self.parse_expr(pre)?, + )) + } + TK_CONCAT => { + eat_assert!(self, TK_CONCAT); + Box::new(Expr::Binary( + result, + Operator::Concat, + self.parse_expr(pre)?, + )) + } + TK_PTR => { + let tok = eat_assert!(self, TK_PTR); + let op = if tok.value.len() == 2 { + Operator::ArrowRight + } else { + Operator::ArrowRightShift + }; + + Box::new(Expr::Binary(result, op, self.parse_expr(pre)?)) + } + TK_COLLATE => Box::new(Expr::Collate(result, self.parse_collate()?.unwrap())), + _ => unreachable!(), + } + } + + Ok(result) + } + + fn parse_collate(&mut self) -> Result, Error> { + if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_COLLATE) { + eat_assert!(self, TK_COLLATE); + } else { + return Ok(None); + } + } else { + return Ok(None); + } + + let tok = eat_expect!(self, TK_ID, TK_STRING); + let first_char = tok.value[0]; // no need to check empty + match first_char { + b'[' | b'\'' | b'`' | b'"' => Ok(Some(Name::Quoted(from_bytes(tok.value)))), + _ => Ok(Some(Name::Ident(from_bytes(tok.value)))), + } + } + + fn parse_sort_order(&mut self) -> Result, Error> { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_ASC) => { + eat_assert!(self, TK_ASC); + Ok(Some(SortOrder::Asc)) + } + Some(tok) if tok.token_type == Some(TK_DESC) => { + eat_assert!(self, TK_DESC); + Ok(Some(SortOrder::Desc)) + } + _ => Ok(None), + } + } + + fn parse_eid(&mut self) -> Result { + let nm = self.parse_nm()?; + let collate = self.parse_collate()?; + let sort_order = self.parse_sort_order()?; + Ok(IndexedColumn { + col_name: nm, + collation_name: collate, + order: sort_order, + }) + } + + fn parse_eid_list(&mut self) -> Result, Error> { + if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_LP) { + eat_assert!(self, TK_LP); + } else { + return Ok(vec![]); + } + } else { + return Ok(vec![]); + } + + let mut columns = vec![self.parse_eid()?]; + loop { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_COMMA) => { + eat_assert!(self, TK_COMMA); + columns.push(self.parse_eid()?); + } + _ => break, + } + } + eat_expect!(self, TK_RP); + + Ok(columns) + } + + fn parse_common_table_expr(&mut self) -> Result { + let nm = self.parse_nm()?; + let eid_list = self.parse_eid_list()?; + eat_expect!(self, TK_AS); + let wqas = match self.peek_no_eof()?.token_type.unwrap() { + TK_MATERIALIZED => { + eat_assert!(self, TK_MATERIALIZED); + Materialized::Yes + } + TK_NOT => { + eat_assert!(self, TK_NOT); + eat_expect!(self, TK_MATERIALIZED); + Materialized::No + } + _ => Materialized::Any, + }; + eat_expect!(self, TK_LP); + let select = self.parse_select()?; + eat_expect!(self, TK_RP); + Ok(CommonTableExpr { + tbl_name: nm, + columns: eid_list, + materialized: wqas, + select, + }) + } + + fn parse_with(&mut self) -> Result, Error> { + if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_WITH) { + eat_assert!(self, TK_WITH); + } else { + return Ok(None); + } + } else { + return Ok(None); + } + + let recursive = if self.peek_no_eof()?.token_type == Some(TK_RECURSIVE) { + eat_assert!(self, TK_RECURSIVE); + true + } else { + false + }; + + let mut ctes = vec![self.parse_common_table_expr()?]; + + loop { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_COMMA) => { + eat_assert!(self, TK_COMMA); + ctes.push(self.parse_common_table_expr()?); + } + _ => break, + } + } + + Ok(Some(With { recursive, ctes })) + } + + fn parse_as(&mut self) -> Result, Error> { + match self.peek()? { + None => Ok(None), + Some(tok) => match tok.token_type.unwrap().fallback_id_if_ok() { + TK_AS => { + eat_assert!(self, TK_AS); + Ok(Some(As::As(self.parse_nm()?))) + } + TK_STRING | TK_ID => Ok(Some(As::Elided(self.parse_nm()?))), + _ => Ok(None), + }, + } + } + + fn parse_window_defn(&mut self) -> Result { + let name = self.parse_nm()?; + eat_expect!(self, TK_AS); + eat_expect!(self, TK_LP); + let window = self.parse_window()?; + eat_expect!(self, TK_RP); + Ok(WindowDef { name, window }) + } + + fn parse_window_clause(&mut self) -> Result, Error> { + match self.peek()? { + None => return Ok(vec![]), + Some(tok) => match tok.token_type.unwrap() { + TK_WINDOW => { + eat_assert!(self, TK_WINDOW); + } + _ => return Ok(vec![]), + }, + } + + let mut result = vec![self.parse_window_defn()?]; + while let Some(tok) = self.peek()? { + match tok.token_type.unwrap() { + TK_COMMA => { + eat_assert!(self, TK_COMMA); + result.push(self.parse_window_defn()?); + } + _ => break, + } + } + + Ok(result) + } + + fn parse_group_by(&mut self) -> Result, Error> { + match self.peek()? { + None => return Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_GROUP => { + eat_assert!(self, TK_GROUP); + eat_expect!(self, TK_BY); + } + _ => return Ok(None), + }, + } + + let exprs = self.parse_nexpr_list()?; + let having = match self.peek()? { + Some(tok) if tok.token_type == Some(TK_HAVING) => { + eat_assert!(self, TK_HAVING); + Some(self.parse_expr(0)?) + } + _ => None, + }; + + Ok(Some(GroupBy { exprs, having })) + } + + fn parse_where(&mut self) -> Result>, Error> { + match self.peek()? { + None => Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_WHERE => { + eat_assert!(self, TK_WHERE); + let expr = self.parse_expr(0)?; + Ok(Some(expr)) + } + _ => Ok(None), + }, + } + } + + fn parse_indexed(&mut self) -> Result, Error> { + match self.peek()? { + None => Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_INDEXED => { + eat_assert!(self, TK_INDEXED); + eat_expect!(self, TK_BY); + Ok(Some(Indexed::IndexedBy(self.parse_nm()?))) + } + TK_NOT => { + eat_assert!(self, TK_NOT); + eat_expect!(self, TK_INDEXED); + Ok(Some(Indexed::NotIndexed)) + } + _ => Ok(None), + }, + } + } + + fn parse_nm_list(&mut self) -> Result, Error> { + let mut names = vec![self.parse_nm()?]; + + loop { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_COMMA) => { + eat_assert!(self, TK_COMMA); + names.push(self.parse_nm()?); + } + _ => break, + } + } + + Ok(names) + } + + fn parse_nm_list_opt(&mut self) -> Result, Error> { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_LP) => { + eat_assert!(self, TK_LP); + } + _ => return Ok(vec![]), + } + + let result = self.parse_nm_list()?; + eat_expect!(self, TK_RP); + Ok(result) + } + + fn parse_on_using(&mut self) -> Result, Error> { + match self.peek()? { + None => Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_ON => { + eat_assert!(self, TK_ON); + let expr = self.parse_expr(0)?; + Ok(Some(JoinConstraint::On(expr))) + } + TK_USING => { + eat_assert!(self, TK_USING); + eat_expect!(self, TK_LP); + let names = self.parse_nm_list()?; + eat_expect!(self, TK_RP); + Ok(Some(JoinConstraint::Using(names))) + } + _ => Ok(None), + }, + } + } + + fn parse_joined_tables(&mut self) -> Result, Error> { + let mut result = vec![]; + while let Some(tok) = self.peek()? { + let op = match tok.token_type.unwrap() { + TK_COMMA => { + eat_assert!(self, TK_COMMA); + JoinOperator::Comma + } + TK_JOIN => { + eat_assert!(self, TK_JOIN); + JoinOperator::TypedJoin(None) + } + TK_JOIN_KW => { + let jkw = eat_assert!(self, TK_JOIN_KW); + let tok = eat_expect!(self, TK_JOIN, TK_ID, TK_STRING, TK_INDEXED, TK_JOIN_KW); + + match tok.token_type.unwrap() { + TK_JOIN => { + JoinOperator::TypedJoin(Some(new_join_type(jkw.value, None, None)?)) + } + _ => { + let name_1 = tok.value; + let tok = eat_expect!( + self, TK_JOIN, TK_ID, TK_STRING, TK_INDEXED, TK_JOIN_KW, + ); + + match tok.token_type.unwrap() { + TK_JOIN => JoinOperator::TypedJoin(Some(new_join_type( + jkw.value, + Some(name_1), + None, + )?)), + _ => { + let name_2 = tok.value; + eat_expect!(self, TK_JOIN); + JoinOperator::TypedJoin(Some(new_join_type( + jkw.value, + Some(name_1), + Some(name_2), + )?)) + } + } + } + } + } + _ => break, + }; + + let tok = peek_expect!(self, TK_ID, TK_STRING, TK_INDEXED, TK_JOIN_KW, TK_LP); + + match tok.token_type.unwrap().fallback_id_if_ok() { + TK_ID | TK_STRING | TK_INDEXED | TK_JOIN_KW => { + let name = self.parse_fullname(false)?; + match self.peek()? { + None => { + result.push(JoinedSelectTable { + operator: op, + table: Box::new(SelectTable::Table(name, None, None)), + constraint: None, + }); + } + Some(tok) => match tok.token_type.unwrap() { + TK_LP => { + eat_assert!(self, TK_LP); + let exprs = self.parse_expr_list()?; + eat_expect!(self, TK_RP); + let alias = self.parse_as()?; + let on_using = self.parse_on_using()?; + result.push(JoinedSelectTable { + operator: op, + table: Box::new(SelectTable::TableCall(name, exprs, alias)), + constraint: on_using, + }); + } + _ => { + let alias = self.parse_as()?; + let indexed = self.parse_indexed()?; + let on_using = self.parse_on_using()?; + result.push(JoinedSelectTable { + operator: op, + table: Box::new(SelectTable::Table(name, alias, indexed)), + constraint: on_using, + }); + } + }, + } + } + TK_LP => { + eat_assert!(self, TK_LP); + match self.peek_no_eof()?.token_type.unwrap() { + TK_SELECT | TK_WITH | TK_VALUES => { + let select = self.parse_select()?; + eat_expect!(self, TK_RP); + let alias = self.parse_as()?; + let on_using = self.parse_on_using()?; + result.push(JoinedSelectTable { + operator: op, + table: Box::new(SelectTable::Select(select, alias)), + constraint: on_using, + }); + } + _ => { + let fr = self.parse_from_clause()?; + eat_expect!(self, TK_RP); + let alias = self.parse_as()?; + let on_using = self.parse_on_using()?; + result.push(JoinedSelectTable { + operator: op, + table: Box::new(SelectTable::Sub(fr, alias)), + constraint: on_using, + }); + } + } + } + _ => unreachable!(), + } + } + + Ok(result) + } + + fn parse_from_clause(&mut self) -> Result { + let tok = peek_expect!(self, TK_ID, TK_STRING, TK_INDEXED, TK_JOIN_KW, TK_LP); + + match tok.token_type.unwrap().fallback_id_if_ok() { + TK_ID | TK_STRING | TK_INDEXED | TK_JOIN_KW => { + let name = self.parse_fullname(false)?; + match self.peek()? { + None => Ok(FromClause { + select: Box::new(SelectTable::Table(name, None, None)), + joins: vec![], + }), + Some(tok) => match tok.token_type.unwrap() { + TK_LP => { + eat_assert!(self, TK_LP); + let exprs = self.parse_expr_list()?; + eat_expect!(self, TK_RP); + let alias = self.parse_as()?; + Ok(FromClause { + select: Box::new(SelectTable::TableCall(name, exprs, alias)), + joins: self.parse_joined_tables()?, + }) + } + _ => { + let alias = self.parse_as()?; + let indexed = self.parse_indexed()?; + Ok(FromClause { + select: Box::new(SelectTable::Table(name, alias, indexed)), + joins: self.parse_joined_tables()?, + }) + } + }, + } + } + TK_LP => { + eat_assert!(self, TK_LP); + match self.peek_no_eof()?.token_type.unwrap() { + TK_SELECT | TK_WITH | TK_VALUES => { + let select = self.parse_select()?; + eat_expect!(self, TK_RP); + let alias = self.parse_as()?; + Ok(FromClause { + select: Box::new(SelectTable::Select(select, alias)), + joins: self.parse_joined_tables()?, + }) + } + _ => { + let fr = self.parse_from_clause()?; + eat_expect!(self, TK_RP); + let alias = self.parse_as()?; + Ok(FromClause { + select: Box::new(SelectTable::Sub(fr, alias)), + joins: self.parse_joined_tables()?, + }) + } + } + } + _ => unreachable!(), + } + } + + fn parse_from_clause_opt(&mut self) -> Result, Error> { + match self.peek()? { + None => return Ok(None), + Some(tok) if tok.token_type == Some(TK_FROM) => { + eat_assert!(self, TK_FROM); + } + _ => return Ok(None), + } + + Ok(Some(self.parse_from_clause()?)) + } + + fn parse_select_column(&mut self) -> Result { + match self.peek_no_eof()?.token_type.unwrap().fallback_id_if_ok() { + TK_STAR => { + eat_assert!(self, TK_STAR); + Ok(ResultColumn::Star) + } + tt => { + // dot STAR case + if tt == TK_ID || tt == TK_STRING || tt == TK_INDEXED || tt == TK_JOIN_KW { + if let Ok(res) = self.mark(|p| -> Result { + let name = p.parse_nm()?; + eat_expect!(p, TK_DOT); + eat_expect!(p, TK_STAR); + Ok(ResultColumn::TableStar(name)) + }) { + return Ok(res); + } + } + + let expr = self.parse_expr(0)?; + let alias = self.parse_as()?; + Ok(ResultColumn::Expr(expr, alias)) + } + } + } + + fn parse_select_columns(&mut self) -> Result, Error> { + let mut result = vec![self.parse_select_column()?]; + + while let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_COMMA) { + eat_assert!(self, TK_COMMA); + } else { + break; + } + + result.push(self.parse_select_column()?); + } + + Ok(result) + } + + #[allow(clippy::vec_box)] + fn parse_nexpr_list(&mut self) -> Result>, Error> { + let mut result = vec![self.parse_expr(0)?]; + while let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_COMMA) { + eat_assert!(self, TK_COMMA); + } else { + break; + } + + result.push(self.parse_expr(0)?); + } + + Ok(result) + } + + fn parse_one_select(&mut self) -> Result { + let tok = eat_expect!(self, TK_SELECT, TK_VALUES); + match tok.token_type.unwrap() { + TK_SELECT => { + let distinct = self.parse_distinct()?; + let collist = self.parse_select_columns()?; + let from = self.parse_from_clause_opt()?; + let where_clause = self.parse_where()?; + let group_by = self.parse_group_by()?; + let window_clause = self.parse_window_clause()?; + Ok(OneSelect::Select { + distinctness: distinct, + columns: collist, + from, + where_clause, + group_by, + window_clause, + }) + } + TK_VALUES => { + eat_expect!(self, TK_LP); + let mut values = vec![self.parse_nexpr_list()?]; + eat_expect!(self, TK_RP); + + while let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_COMMA) { + eat_assert!(self, TK_COMMA); + } else { + break; + } + + eat_expect!(self, TK_LP); + values.push(self.parse_nexpr_list()?); + eat_expect!(self, TK_RP); + } + + Ok(OneSelect::Values(values)) + } + _ => unreachable!(), + } + } + + fn parse_select_body(&mut self) -> Result { + let select = self.parse_one_select()?; + let mut compounds = vec![]; + while let Some(tok) = self.peek()? { + let op = match tok.token_type.unwrap() { + TK_UNION => { + eat_assert!(self, TK_UNION); + if self.peek_no_eof()?.token_type == Some(TK_ALL) { + eat_assert!(self, TK_ALL); + CompoundOperator::UnionAll + } else { + CompoundOperator::Union + } + } + TK_EXCEPT => { + eat_assert!(self, TK_EXCEPT); + CompoundOperator::Except + } + TK_INTERSECT => { + eat_assert!(self, TK_INTERSECT); + CompoundOperator::Intersect + } + _ => break, + }; + + compounds.push(CompoundSelect { + operator: op, + select: self.parse_one_select()?, + }); + } + + Ok(SelectBody { select, compounds }) + } + + fn parse_sorted_column(&mut self) -> Result { + let expr = self.parse_expr(0)?; + let sort_order = self.parse_sort_order()?; + + let nulls = match self.peek()? { + Some(tok) if tok.token_type == Some(TK_NULLS) => { + eat_assert!(self, TK_NULLS); + let tok = eat_expect!(self, TK_FIRST, TK_LAST); + match tok.token_type.unwrap() { + TK_FIRST => Some(NullsOrder::First), + TK_LAST => Some(NullsOrder::Last), + _ => unreachable!(), + } + } + _ => None, + }; + + Ok(SortedColumn { + expr, + order: sort_order, + nulls, + }) + } + + fn parse_sort_list(&mut self) -> Result, Error> { + let mut columns = vec![self.parse_sorted_column()?]; + loop { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_COMMA) => { + eat_assert!(self, TK_COMMA); + columns.push(self.parse_sorted_column()?); + } + _ => break, + } + } + + Ok(columns) + } + + fn parse_order_by(&mut self) -> Result, Error> { + if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_ORDER) { + eat_assert!(self, TK_ORDER); + } else { + return Ok(vec![]); + } + } else { + return Ok(vec![]); + } + + eat_expect!(self, TK_BY); + self.parse_sort_list() + } + + fn parse_limit(&mut self) -> Result, Error> { + if let Some(tok) = self.peek()? { + if tok.token_type == Some(TK_LIMIT) { + eat_assert!(self, TK_LIMIT); + } else { + return Ok(None); + } + } else { + return Ok(None); + } + + let limit = self.parse_expr(0)?; + let offset = match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_OFFSET | TK_COMMA => { + eat_assert!(self, TK_OFFSET, TK_COMMA); + Some(self.parse_expr(0)?) + } + _ => None, + }, + _ => None, + }; + + Ok(Some(Limit { + expr: limit, + offset, + })) + } + + fn parse_select_without_cte(&mut self, with: Option) -> Result { + let body = self.parse_select_body()?; + let order_by = self.parse_order_by()?; + let limit = self.parse_limit()?; + Ok(Select { + with, + body, + order_by, + limit, + }) + } + + fn parse_select(&mut self) -> Result { + let with = self.parse_with()?; + self.parse_select_without_cte(with) + } + + fn parse_primary_table_constraint(&mut self) -> Result { + eat_assert!(self, TK_PRIMARY); + eat_expect!(self, TK_KEY); + eat_expect!(self, TK_LP); + let columns = self.parse_sort_list()?; + let auto_increment = self.parse_auto_increment()?; + eat_expect!(self, TK_RP); + let conflict_clause = self.parse_on_conflict()?; + Ok(TableConstraint::PrimaryKey { + columns, + auto_increment, + conflict_clause, + }) + } + + fn parse_unique_table_constraint(&mut self) -> Result { + eat_assert!(self, TK_UNIQUE); + eat_expect!(self, TK_LP); + let columns = self.parse_sort_list()?; + eat_expect!(self, TK_RP); + let conflict_clause = self.parse_on_conflict()?; + Ok(TableConstraint::Unique { + columns, + conflict_clause, + }) + } + + fn parse_check_table_constraint(&mut self) -> Result { + eat_assert!(self, TK_CHECK); + eat_expect!(self, TK_LP); + let expr = self.parse_expr(0)?; + eat_expect!(self, TK_RP); + Ok(TableConstraint::Check(expr)) + } + + fn parse_foreign_key_table_constraint(&mut self) -> Result { + eat_assert!(self, TK_FOREIGN); + eat_expect!(self, TK_KEY); + peek_expect!(self, TK_LP); // make sure we have columns + let columns = self.parse_eid_list()?; + peek_expect!(self, TK_REFERENCES); + let clause = self.parse_foreign_key_clause()?; + let deref_clause = self.parse_defer_subclause()?; + Ok(TableConstraint::ForeignKey { + columns, + clause, + deref_clause, + }) + } + + fn parse_named_table_constraints(&mut self) -> Result, Error> { + let mut result = vec![]; + + while let Some(tok) = self.peek()? { + match tok.token_type.unwrap() { + TK_COMMA => { + eat_assert!(self, TK_COMMA); + } + TK_CONSTRAINT | TK_PRIMARY | TK_UNIQUE | TK_CHECK | TK_FOREIGN => {} + _ => break, + } + + let name = match self.peek_no_eof()?.token_type.unwrap() { + TK_CONSTRAINT => { + eat_assert!(self, TK_CONSTRAINT); + Some(self.parse_nm()?) + } + _ => None, + }; + + let tok = peek_expect!(self, TK_PRIMARY, TK_UNIQUE, TK_CHECK, TK_FOREIGN); + + match tok.token_type.unwrap() { + TK_PRIMARY => { + result.push(NamedTableConstraint { + name, + constraint: self.parse_primary_table_constraint()?, + }); + } + TK_UNIQUE => { + result.push(NamedTableConstraint { + name, + constraint: self.parse_unique_table_constraint()?, + }); + } + TK_CHECK => { + result.push(NamedTableConstraint { + name, + constraint: self.parse_check_table_constraint()?, + }); + } + TK_FOREIGN => { + result.push(NamedTableConstraint { + name, + constraint: self.parse_foreign_key_table_constraint()?, + }); + } + _ => unreachable!(), + } + } + + Ok(result) + } + + fn parse_table_option(&mut self) -> Result { + match self.peek()? { + Some(tok) => match tok.token_type.unwrap().fallback_id_if_ok() { + TK_WITHOUT => { + eat_assert!(self, TK_WITHOUT); + let tok = eat_expect!(self, TK_ID); + if b"ROWID".eq_ignore_ascii_case(tok.value) { + Ok(TableOptions::WITHOUT_ROWID) + } else { + Err(Error::Custom(format!( + "unknown table option: {}", + from_bytes(tok.value) + ))) + } + } + TK_ID => { + let tok = eat_assert!(self, TK_ID); + if b"STRICT".eq_ignore_ascii_case(tok.value) { + Ok(TableOptions::STRICT) + } else { + Err(Error::Custom(format!( + "unknown table option: {}", + from_bytes(tok.value) + ))) + } + } + _ => Ok(TableOptions::NONE), + }, + _ => Ok(TableOptions::NONE), + } + } + + fn parse_table_options(&mut self) -> Result { + let mut result = self.parse_table_option()?; + loop { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_COMMA) => { + eat_assert!(self, TK_COMMA); + result |= self.parse_table_option()?; + } + _ => break, + } + } + + Ok(result) + } + + fn parse_create_table_args(&mut self) -> Result { + let tok = eat_expect!(self, TK_LP, TK_AS); + match tok.token_type.unwrap() { + TK_AS => Ok(CreateTableBody::AsSelect(self.parse_select()?)), + TK_LP => { + let mut columns = vec![self.parse_column_definition()?]; + let mut constraints = vec![]; + loop { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_COMMA) => { + eat_assert!(self, TK_COMMA); + match self.peek_no_eof()?.token_type.unwrap() { + TK_CONSTRAINT | TK_PRIMARY | TK_UNIQUE | TK_CHECK | TK_FOREIGN => { + constraints = self.parse_named_table_constraints()?; + break; + } + _ => { + columns.push(self.parse_column_definition()?); + } + } + } + _ => break, + } + } + + eat_expect!(self, TK_RP); + let options = self.parse_table_options()?; + Ok(CreateTableBody::ColumnsAndConstraints { + columns, + constraints, + options, + }) + } + _ => unreachable!(), + } + } + + fn parse_create_table(&mut self, temporary: bool) -> Result { + eat_assert!(self, TK_TABLE); + let if_not_exists = self.parse_if_not_exists()?; + let tbl_name = self.parse_fullname(false)?; + let body = self.parse_create_table_args()?; + Ok(Stmt::CreateTable { + temporary, + if_not_exists, + tbl_name, + body, + }) + } + + fn parse_analyze(&mut self) -> Result { + eat_assert!(self, TK_ANALYZE); + let name = match self.peek()? { + Some(tok) => match tok.token_type.unwrap().fallback_id_if_ok() { + TK_ID | TK_STRING | TK_INDEXED | TK_JOIN_KW => Some(self.parse_fullname(false)?), + _ => None, + }, + _ => None, + }; + + Ok(Stmt::Analyze { name }) + } + + fn parse_attach(&mut self) -> Result { + eat_assert!(self, TK_ATTACH); + if self.peek_no_eof()?.token_type == Some(TK_DATABASE) { + eat_assert!(self, TK_DATABASE); + } + + let expr = self.parse_expr(0)?; + eat_expect!(self, TK_AS); + let db_name = self.parse_expr(0)?; + let key = match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_KEY => { + eat_assert!(self, TK_KEY); + Some(self.parse_expr(0)?) + } + _ => None, + }, + _ => None, + }; + + Ok(Stmt::Attach { expr, db_name, key }) + } + + fn parse_detach(&mut self) -> Result { + eat_assert!(self, TK_DETACH); + if self.peek_no_eof()?.token_type == Some(TK_DATABASE) { + eat_assert!(self, TK_DATABASE); + } + + Ok(Stmt::Detach { + name: self.parse_expr(0)?, + }) + } + + fn parse_pragma_value(&mut self) -> Result { + match self.peek_no_eof()?.token_type.unwrap().fallback_id_if_ok() { + TK_ON | TK_DELETE | TK_DEFAULT => { + let tok = eat_assert!(self, TK_ON, TK_DELETE, TK_DEFAULT); + Ok(Box::new(Expr::Literal(Literal::Keyword(from_bytes( + tok.value, + ))))) + } + TK_ID | TK_STRING | TK_INDEXED | TK_JOIN_KW => { + Ok(Box::new(Expr::Name(self.parse_nm()?))) + } + _ => self.parse_signed(), + } + } + + fn parse_pragma(&mut self) -> Result { + eat_assert!(self, TK_PRAGMA); + let name = self.parse_fullname(false)?; + match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_EQ => { + eat_assert!(self, TK_EQ); + Ok(Stmt::Pragma { + name, + body: Some(PragmaBody::Equals(self.parse_pragma_value()?)), + }) + } + TK_LP => { + eat_assert!(self, TK_LP); + let value = self.parse_pragma_value()?; + eat_expect!(self, TK_RP); + Ok(Stmt::Pragma { + name, + body: Some(PragmaBody::Call(value)), + }) + } + _ => Ok(Stmt::Pragma { name, body: None }), + }, + _ => Ok(Stmt::Pragma { name, body: None }), + } + } + + fn parse_vacuum(&mut self) -> Result { + eat_assert!(self, TK_VACUUM); + + let name = match self.peek()? { + Some(tok) => match tok.token_type.unwrap().fallback_id_if_ok() { + TK_ID | TK_STRING | TK_INDEXED | TK_JOIN_KW => Some(self.parse_nm()?), + _ => None, + }, + _ => None, + }; + + let into = match self.peek()? { + Some(tok) if tok.token_type == Some(TK_INTO) => { + eat_assert!(self, TK_INTO); + Some(self.parse_expr(0)?) + } + _ => None, + }; + + Ok(Stmt::Vacuum { name, into }) + } + + fn parse_term(&mut self) -> Result, Error> { + peek_expect!( + self, + TK_NULL, + TK_BLOB, + TK_STRING, + TK_FLOAT, + TK_INTEGER, + TK_CTIME_KW, + ); + + self.parse_expr_operand() + } + + fn parse_default_column_constraint(&mut self) -> Result { + eat_assert!(self, TK_DEFAULT); + match self.peek_no_eof()?.token_type.unwrap().fallback_id_if_ok() { + TK_LP => { + eat_assert!(self, TK_LP); + let expr = self.parse_expr(0)?; + eat_expect!(self, TK_RP); + Ok(ColumnConstraint::Default(Box::new(Expr::Parenthesized( + vec![expr], + )))) + } + TK_PLUS => { + eat_assert!(self, TK_PLUS); + Ok(ColumnConstraint::Default(Box::new(Expr::Unary( + UnaryOperator::Positive, + self.parse_term()?, + )))) + } + TK_MINUS => { + eat_assert!(self, TK_MINUS); + Ok(ColumnConstraint::Default(Box::new(Expr::Unary( + UnaryOperator::Negative, + self.parse_term()?, + )))) + } + TK_ID | TK_INDEXED => Ok(ColumnConstraint::Default(Box::new(Expr::Id( + self.parse_nm()?, + )))), + _ => Ok(ColumnConstraint::Default(self.parse_term()?)), + } + } + + fn parse_resolve_type(&mut self) -> Result { + match self.peek_no_eof()?.token_type.unwrap() { + TK_IGNORE => { + eat_assert!(self, TK_IGNORE); + Ok(ResolveType::Ignore) + } + TK_REPLACE => { + eat_assert!(self, TK_REPLACE); + Ok(ResolveType::Replace) + } + _ => Ok(self.parse_raise_type()?), + } + } + + fn parse_on_conflict(&mut self) -> Result, Error> { + match self.peek()? { + None => return Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_ON => { + eat_assert!(self, TK_ON); + eat_expect!(self, TK_CONFLICT); + } + _ => return Ok(None), + }, + } + + Ok(Some(self.parse_resolve_type()?)) + } + + fn parse_or_conflict(&mut self) -> Result, Error> { + match self.peek()? { + None => return Ok(None), + Some(tok) => match tok.token_type.unwrap() { + TK_OR => { + eat_assert!(self, TK_OR); + } + _ => return Ok(None), + }, + } + + Ok(Some(self.parse_resolve_type()?)) + } + + fn parse_auto_increment(&mut self) -> Result { + match self.peek()? { + None => Ok(false), + Some(tok) => match tok.token_type.unwrap() { + TK_AUTOINCR => { + eat_assert!(self, TK_AUTOINCR); + Ok(true) + } + _ => Ok(false), + }, + } + } + + fn parse_not_null_column_constraint(&mut self) -> Result { + let has_not = match self.peek_no_eof()?.token_type.unwrap() { + TK_NOT => { + eat_assert!(self, TK_NOT); + true + } + _ => false, + }; + + eat_expect!(self, TK_NULL); + Ok(ColumnConstraint::NotNull { + nullable: !has_not, + conflict_clause: self.parse_on_conflict()?, + }) + } + + fn parse_primary_column_constraint(&mut self) -> Result { + eat_assert!(self, TK_PRIMARY); + eat_expect!(self, TK_KEY); + let sort_order = self.parse_sort_order()?; + let conflict_clause = self.parse_on_conflict()?; + let autoincr = self.parse_auto_increment()?; + + Ok(ColumnConstraint::PrimaryKey { + order: sort_order, + conflict_clause, + auto_increment: autoincr, + }) + } + + fn parse_unique_column_constraint(&mut self) -> Result { + eat_assert!(self, TK_UNIQUE); + Ok(ColumnConstraint::Unique(self.parse_on_conflict()?)) + } + + fn parse_check_column_constraint(&mut self) -> Result { + eat_assert!(self, TK_CHECK); + eat_expect!(self, TK_LP); + let expr = self.parse_expr(0)?; + eat_expect!(self, TK_RP); + Ok(ColumnConstraint::Check(expr)) + } + + fn parse_ref_act(&mut self) -> Result { + let tok = eat_expect!(self, TK_SET, TK_CASCADE, TK_RESTRICT, TK_NO); + + match tok.token_type.unwrap() { + TK_SET => { + let tok = eat_expect!(self, TK_NULL, TK_DEFAULT); + match tok.token_type.unwrap() { + TK_NULL => Ok(RefAct::SetNull), + TK_DEFAULT => Ok(RefAct::SetDefault), + _ => unreachable!(), + } + } + TK_CASCADE => Ok(RefAct::Cascade), + TK_RESTRICT => Ok(RefAct::Restrict), + TK_NO => { + eat_expect!(self, TK_ACTION); + Ok(RefAct::NoAction) + } + _ => unreachable!(), + } + } + + fn parse_ref_args(&mut self) -> Result, Error> { + let mut result = vec![]; + + while let Some(tok) = self.peek()? { + match tok.token_type.unwrap() { + TK_MATCH => { + eat_assert!(self, TK_MATCH); + result.push(RefArg::Match(self.parse_nm()?)); + } + TK_ON => { + eat_assert!(self, TK_ON); + let tok = eat_expect!(self, TK_INSERT, TK_DELETE, TK_UPDATE); + match tok.token_type.unwrap() { + TK_INSERT => result.push(RefArg::OnInsert(self.parse_ref_act()?)), + TK_DELETE => result.push(RefArg::OnDelete(self.parse_ref_act()?)), + TK_UPDATE => result.push(RefArg::OnUpdate(self.parse_ref_act()?)), + _ => unreachable!(), + } + } + _ => break, + } + } + + Ok(result) + } + + fn parse_foreign_key_clause(&mut self) -> Result { + eat_assert!(self, TK_REFERENCES); + let name = self.parse_nm()?; + let eid_list = self.parse_eid_list()?; + let ref_args = self.parse_ref_args()?; + Ok(ForeignKeyClause { + tbl_name: name, + columns: eid_list, + args: ref_args, + }) + } + + fn parse_defer_subclause(&mut self) -> Result, Error> { + let has_not = match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_DEFERRABLE => false, + TK_NOT => { + eat_assert!(self, TK_NOT); + true + } + _ => return Ok(None), + }, + _ => return Ok(None), + }; + + eat_expect!(self, TK_DEFERRABLE); + + let init = match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_INITIALLY => { + eat_assert!(self, TK_INITIALLY); + let tok = eat_expect!(self, TK_DEFERRED, TK_IMMEDIATE); + match tok.token_type.unwrap() { + TK_DEFERRED => Some(InitDeferredPred::InitiallyDeferred), + TK_IMMEDIATE => Some(InitDeferredPred::InitiallyImmediate), + _ => unreachable!(), + } + } + _ => None, + }, + _ => None, + }; + + Ok(Some(DeferSubclause { + deferrable: !has_not, + init_deferred: init, + })) + } + + fn parse_reference_column_constraint(&mut self) -> Result { + let clause = self.parse_foreign_key_clause()?; + let deref_clause = self.parse_defer_subclause()?; + Ok(ColumnConstraint::ForeignKey { + clause, + deref_clause, + }) + } + + fn parse_generated_column_constraint(&mut self) -> Result { + let tok = eat_assert!(self, TK_GENERATED, TK_AS); + match tok.token_type.unwrap() { + TK_GENERATED => { + eat_expect!(self, TK_ALWAYS); + eat_expect!(self, TK_AS); + } + TK_AS => {} + _ => unreachable!(), + } + + eat_expect!(self, TK_LP); + let expr = self.parse_expr(0)?; + eat_expect!(self, TK_RP); + + let typ = match self.peek()? { + Some(tok) => match tok.token_type.unwrap().fallback_id_if_ok() { + TK_ID => { + let tok = eat_assert!(self, TK_ID); + Some(Name::Ident(from_bytes(tok.value))) + } + _ => None, + }, + _ => None, + }; + + Ok(ColumnConstraint::Generated { expr, typ }) + } + + fn parse_named_column_constraints(&mut self) -> Result, Error> { + let mut result = vec![]; + + loop { + let name = match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_CONSTRAINT => { + eat_assert!(self, TK_CONSTRAINT); + Some(self.parse_nm()?) + } + _ => None, + }, + _ => None, + }; + + if name.is_some() { + peek_expect!( + self, + TK_DEFAULT, + TK_NOT, + TK_NULL, + TK_PRIMARY, + TK_UNIQUE, + TK_CHECK, + TK_REFERENCES, + TK_COLLATE, + TK_GENERATED, + TK_AS, + ); + } + + match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_DEFAULT => { + result.push(NamedColumnConstraint { + name, + constraint: self.parse_default_column_constraint()?, + }); + } + TK_NOT | TK_NULL => { + result.push(NamedColumnConstraint { + name, + constraint: self.parse_not_null_column_constraint()?, + }); + } + TK_PRIMARY => { + result.push(NamedColumnConstraint { + name, + constraint: self.parse_primary_column_constraint()?, + }); + } + TK_UNIQUE => { + result.push(NamedColumnConstraint { + name, + constraint: self.parse_unique_column_constraint()?, + }); + } + TK_CHECK => { + result.push(NamedColumnConstraint { + name, + constraint: self.parse_check_column_constraint()?, + }); + } + TK_REFERENCES => { + result.push(NamedColumnConstraint { + name, + constraint: self.parse_reference_column_constraint()?, + }); + } + TK_COLLATE => { + result.push(NamedColumnConstraint { + name, + constraint: ColumnConstraint::Collate { + collation_name: self.parse_collate()?.unwrap(), + }, + }); + } + TK_GENERATED | TK_AS => { + result.push(NamedColumnConstraint { + name, + constraint: self.parse_generated_column_constraint()?, + }); + } + _ => break, + }, + _ => break, + } + } + + Ok(result) + } + + fn parse_column_definition(&mut self) -> Result { + let col_name = self.parse_nm()?; + let col_type = self.parse_type()?; + let constraints = self.parse_named_column_constraints()?; + Ok(ColumnDefinition { + col_name, + col_type, + constraints, + }) + } + + fn parse_alter(&mut self) -> Result { + eat_assert!(self, TK_ALTER); + eat_expect!(self, TK_TABLE); + let tbl_name = self.parse_fullname(false)?; + let tok = eat_expect!(self, TK_ADD, TK_DROP, TK_RENAME); + + match tok.token_type.unwrap() { + TK_ADD => { + if self.peek_no_eof()?.token_type == Some(TK_COLUMNKW) { + eat_assert!(self, TK_COLUMNKW); + } + + Ok(Stmt::AlterTable { + name: tbl_name, + body: AlterTableBody::AddColumn(self.parse_column_definition()?), + }) + } + TK_DROP => { + if self.peek_no_eof()?.token_type == Some(TK_COLUMNKW) { + eat_assert!(self, TK_COLUMNKW); + } + + Ok(Stmt::AlterTable { + name: tbl_name, + body: AlterTableBody::DropColumn(self.parse_nm()?), + }) + } + TK_RENAME => { + let col_name = match self.peek_no_eof()?.token_type.unwrap().fallback_id_if_ok() { + TK_COLUMNKW => { + eat_assert!(self, TK_COLUMNKW); + Some(self.parse_nm()?) + } + TK_ID | TK_STRING | TK_INDEXED | TK_JOIN_KW => Some(self.parse_nm()?), + _ => None, + }; + + eat_expect!(self, TK_TO); + let to_name = self.parse_nm()?; + + if let Some(col_name) = col_name { + Ok(Stmt::AlterTable { + name: tbl_name, + body: AlterTableBody::RenameColumn { + old: col_name, + new: to_name, + }, + }) + } else { + Ok(Stmt::AlterTable { + name: tbl_name, + body: AlterTableBody::RenameTo(to_name), + }) + } + } + _ => unreachable!(), + } + } + + fn parse_create_index(&mut self) -> Result { + let tok = eat_assert!(self, TK_INDEX, TK_UNIQUE); + let has_unique = tok.token_type == Some(TK_UNIQUE); + if has_unique { + eat_expect!(self, TK_INDEX); + } + + let if_not_exists = self.parse_if_not_exists()?; + let idx_name = self.parse_fullname(false)?; + eat_expect!(self, TK_ON); + let tbl_name = self.parse_nm()?; + eat_expect!(self, TK_LP); + let columns = self.parse_sort_list()?; + eat_expect!(self, TK_RP); + let where_clause = self.parse_where()?; + + Ok(Stmt::CreateIndex { + if_not_exists, + idx_name, + tbl_name, + columns, + where_clause, + unique: has_unique, + }) + } + + fn parse_set(&mut self) -> Result { + let tok = peek_expect!(self, TK_LP, TK_ID, TK_STRING, TK_JOIN_KW, TK_INDEXED); + + match tok.token_type.unwrap() { + TK_LP => { + eat_assert!(self, TK_LP); + let names = self.parse_nm_list()?; + eat_expect!(self, TK_RP); + eat_expect!(self, TK_EQ); + Ok(Set { + col_names: names, + expr: self.parse_expr(0)?, + }) + } + _ => { + let name = self.parse_nm()?; + eat_expect!(self, TK_EQ); + Ok(Set { + col_names: vec![name], + expr: self.parse_expr(0)?, + }) + } + } + } + + fn parse_set_list(&mut self) -> Result, Error> { + let mut results = vec![self.parse_set()?]; + loop { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_COMMA) => { + eat_assert!(self, TK_COMMA); + results.push(self.parse_set()?); + } + _ => break, + } + } + + Ok(results) + } + + fn parse_returning(&mut self) -> Result, Error> { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_RETURNING) => { + eat_assert!(self, TK_RETURNING); + } + _ => return Ok(vec![]), + } + + self.parse_select_columns() + } + + fn parse_upsert(&mut self) -> Result<(Option>, Vec), Error> { + match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TK_ON => { + eat_assert!(self, TK_ON); + } + TK_RETURNING => { + return Ok((None, self.parse_returning()?)); + } + _ => return Ok((None, vec![])), + }, + _ => return Ok((None, vec![])), + } + + eat_expect!(self, TK_CONFLICT); + let targets = match self.peek_no_eof()?.token_type.unwrap() { + TK_LP => { + eat_assert!(self, TK_LP); + let result = self.parse_sort_list()?; + eat_expect!(self, TK_RP); + result + } + _ => vec![], + }; + + let where_clause = if !targets.is_empty() { + self.parse_where()? + } else { + None + }; + + eat_expect!(self, TK_DO); + + let tok = eat_expect!(self, TK_NOTHING, TK_UPDATE); + let do_clause = match tok.token_type.unwrap() { + TK_NOTHING => UpsertDo::Nothing, + TK_UPDATE => { + eat_expect!(self, TK_SET); + let set_list = self.parse_set_list()?; + let update_where_clause = self.parse_where()?; + UpsertDo::Set { + sets: set_list, + where_clause: update_where_clause, + } + } + _ => unreachable!(), + }; + + if !targets.is_empty() { + let (next, returning) = self.parse_upsert()?; + Ok(( + Some(Box::new(Upsert { + index: Some(UpsertIndex { + targets, + where_clause, + }), + do_clause, + next, + })), + returning, + )) + } else { + Ok(( + Some(Box::new(Upsert { + index: None, + do_clause, + next: None, + })), + self.parse_returning()?, + )) + } + } + + fn parse_trigger_insert_cmd(&mut self) -> Result { + let tok = eat_assert!(self, TK_INSERT, TK_REPLACE); + let resolve_type = match tok.token_type.unwrap() { + TK_INSERT => self.parse_or_conflict()?, + TK_REPLACE => Some(ResolveType::Replace), + _ => unreachable!(), + }; + + eat_expect!(self, TK_INTO); + let tbl_name = self.parse_nm()?; + let col_names = self.parse_nm_list_opt()?; + let select = self.parse_select()?; + let (upsert, returning) = self.parse_upsert()?; + Ok(TriggerCmd::Insert { + or_conflict: resolve_type, + tbl_name, + col_names, + select, + upsert, + returning, + }) + } + + fn parse_trigger_update_cmd(&mut self) -> Result { + eat_assert!(self, TK_UPDATE); + let or_conflict = self.parse_or_conflict()?; + let tbl_name = self.parse_nm()?; + eat_expect!(self, TK_SET); + let sets = self.parse_set_list()?; + let from = self.parse_from_clause_opt()?; + let where_clause = self.parse_where()?; + Ok(TriggerCmd::Update { + or_conflict, + tbl_name, + sets, + from, + where_clause, + }) + } + + fn parse_trigger_delete_cmd(&mut self) -> Result { + eat_assert!(self, TK_DELETE); + eat_expect!(self, TK_FROM); + let tbl_name = self.parse_nm()?; + let where_clause = self.parse_where()?; + Ok(TriggerCmd::Delete { + tbl_name, + where_clause, + }) + } + + fn parse_trigger_cmd(&mut self) -> Result { + let tok = peek_expect!( + self, TK_INSERT, TK_REPLACE, TK_UPDATE, TK_DELETE, TK_WITH, TK_SELECT, TK_VALUES, + ); + + let result = match tok.token_type.unwrap() { + TK_WITH | TK_SELECT | TK_VALUES => TriggerCmd::Select(self.parse_select()?), + TK_INSERT | TK_REPLACE => self.parse_trigger_insert_cmd()?, + TK_UPDATE => self.parse_trigger_update_cmd()?, + TK_DELETE => self.parse_trigger_delete_cmd()?, + _ => unreachable!(), + }; + + eat_expect!(self, TK_SEMI); + Ok(result) + } + + fn parse_create_trigger(&mut self, temporary: bool) -> Result { + eat_assert!(self, TK_TRIGGER); + + let if_not_exists = self.parse_if_not_exists()?; + let trigger_name = self.parse_fullname(false)?; + + let trigger_time = match self.peek_no_eof()?.token_type.unwrap() { + TK_BEFORE => { + eat_assert!(self, TK_BEFORE); + Some(TriggerTime::Before) + } + TK_AFTER => { + eat_assert!(self, TK_AFTER); + Some(TriggerTime::After) + } + TK_INSTEAD => { + eat_assert!(self, TK_INSTEAD); + eat_expect!(self, TK_OF); + Some(TriggerTime::InsteadOf) + } + _ => None, + }; + + let tok = eat_expect!(self, TK_INSERT, TK_UPDATE, TK_DELETE); + let trigger_event = match tok.token_type.unwrap() { + TK_INSERT => TriggerEvent::Insert, + TK_DELETE => TriggerEvent::Delete, + TK_UPDATE => match self.peek_no_eof()?.token_type.unwrap() { + TK_OF => { + eat_assert!(self, TK_OF); + TriggerEvent::UpdateOf(self.parse_nm_list()?) + } + _ => TriggerEvent::Update, + }, + _ => unreachable!(), + }; + + eat_expect!(self, TK_ON); + let tbl_name = self.parse_fullname(false)?; + + let foreach_clause = match self.peek_no_eof()?.token_type.unwrap() { + TK_FOR => { + eat_assert!(self, TK_FOR); + eat_expect!(self, TK_EACH); + eat_expect!(self, TK_ROW); + true + } + _ => false, + }; + + let when_clause = match self.peek()? { + Some(tok) if tok.token_type == Some(TK_WHEN) => { + eat_assert!(self, TK_WHEN); + Some(self.parse_expr(0)?) + } + _ => None, + }; + + eat_expect!(self, TK_BEGIN); + + let mut cmds = vec![self.parse_trigger_cmd()?]; + while let TK_UPDATE | TK_REPLACE | TK_INSERT | TK_DELETE | TK_WITH | TK_SELECT | TK_VALUES = + self.peek_no_eof()?.token_type.unwrap() + { + cmds.push(self.parse_trigger_cmd()?); + } + + eat_expect!(self, TK_END); + + Ok(Stmt::CreateTrigger { + temporary, + if_not_exists, + trigger_name, + time: trigger_time, + event: trigger_event, + tbl_name, + for_each_row: foreach_clause, + when_clause, + commands: cmds, + }) + } + + fn parse_delete_without_cte(&mut self, with: Option) -> Result { + eat_assert!(self, TK_DELETE); + eat_expect!(self, TK_FROM); + let tbl_name = self.parse_fullname(true)?; + let indexed = self.parse_indexed()?; + let where_clause = self.parse_where()?; + let returning = self.parse_returning()?; + let order_by = self.parse_order_by()?; + let limit = self.parse_limit()?; + Ok(Stmt::Delete { + with, + tbl_name, + indexed, + where_clause, + returning, + order_by, + limit, + }) + } + + fn parse_delete(&mut self) -> Result { + let with = self.parse_with()?; + self.parse_delete_without_cte(with) + } + + fn parse_if_exists(&mut self) -> Result { + match self.peek()? { + Some(tok) if tok.token_type == Some(TK_IF) => { + eat_assert!(self, TK_IF); + eat_expect!(self, TK_EXISTS); + Ok(true) + } + _ => Ok(false), + } + } + + fn parse_drop_stmt(&mut self) -> Result { + eat_assert!(self, TK_DROP); + let tok = peek_expect!(self, TK_TABLE, TK_INDEX, TK_TRIGGER, TK_VIEW); + + match tok.token_type.unwrap() { + TK_TABLE => { + eat_assert!(self, TK_TABLE); + let if_exists = self.parse_if_exists()?; + let tbl_name = self.parse_fullname(false)?; + Ok(Stmt::DropTable { + if_exists, + tbl_name, + }) + } + TK_INDEX => { + eat_assert!(self, TK_INDEX); + let if_exists = self.parse_if_exists()?; + let idx_name = self.parse_fullname(false)?; + Ok(Stmt::DropIndex { + if_exists, + idx_name, + }) + } + TK_TRIGGER => { + eat_assert!(self, TK_TRIGGER); + let if_exists = self.parse_if_exists()?; + let trigger_name = self.parse_fullname(false)?; + Ok(Stmt::DropTrigger { + if_exists, + trigger_name, + }) + } + TK_VIEW => { + eat_assert!(self, TK_VIEW); + let if_exists = self.parse_if_exists()?; + let view_name = self.parse_fullname(false)?; + Ok(Stmt::DropView { + if_exists, + view_name, + }) + } + _ => unreachable!(), + } + } + + fn parse_insert_without_cte(&mut self, with: Option) -> Result { + let tok = eat_assert!(self, TK_INSERT, TK_REPLACE); + let resolve_type = match tok.token_type.unwrap() { + TK_INSERT => self.parse_or_conflict()?, + TK_REPLACE => Some(ResolveType::Replace), + _ => unreachable!(), + }; + + eat_expect!(self, TK_INTO); + let tbl_name = self.parse_fullname(true)?; + let columns = self.parse_nm_list_opt()?; + let (body, returning) = match self.peek_no_eof()?.token_type.unwrap() { + TK_DEFAULT => { + eat_assert!(self, TK_DEFAULT); + eat_expect!(self, TK_VALUES); + (InsertBody::DefaultValues, self.parse_returning()?) + } + _ => { + let select = self.parse_select()?; + let (upsert, returning) = self.parse_upsert()?; + (InsertBody::Select(select, upsert), returning) + } + }; + + Ok(Stmt::Insert { + with, + or_conflict: resolve_type, + tbl_name, + columns, + body, + returning, + }) + } + + fn parse_insert(&mut self) -> Result { + let with = self.parse_with()?; + self.parse_insert_without_cte(with) + } + + fn parse_update_without_cte(&mut self, with: Option) -> Result { + eat_assert!(self, TK_UPDATE); + let resolve_type = self.parse_or_conflict()?; + let tbl_name = self.parse_fullname(true)?; + let indexed = self.parse_indexed()?; + eat_expect!(self, TK_SET); + let sets = self.parse_set_list()?; + let from = self.parse_from_clause_opt()?; + let where_clause = self.parse_where()?; + let returning = self.parse_returning()?; + let order_by = self.parse_order_by()?; + let limit = self.parse_limit()?; + Ok(Stmt::Update { + with, + or_conflict: resolve_type, + tbl_name, + indexed, + sets, + from, + where_clause, + returning, + order_by, + limit, + }) + } + + fn parse_update(&mut self) -> Result { + let with = self.parse_with()?; + self.parse_update_without_cte(with) + } + + fn parse_reindex(&mut self) -> Result { + eat_assert!(self, TK_REINDEX); + match self.peek()? { + Some(tok) => match tok.token_type.unwrap().fallback_id_if_ok() { + TK_ID | TK_STRING | TK_JOIN_KW | TK_INDEXED => Ok(Stmt::Reindex { + name: Some(self.parse_fullname(false)?), + }), + _ => Ok(Stmt::Reindex { name: None }), + }, + _ => Ok(Stmt::Reindex { name: None }), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parser() { + let test_cases = vec![ + // begin + ( + b"BEGIN".as_slice(), + vec![Cmd::Stmt(Stmt::Begin { + typ: None, + name: None, + })], + ), + ( + b"EXPLAIN BEGIN".as_slice(), + vec![Cmd::Explain(Stmt::Begin { + typ: None, + name: None, + })], + ), + ( + b"EXPLAIN QUERY PLAN BEGIN".as_slice(), + vec![Cmd::ExplainQueryPlan(Stmt::Begin { + typ: None, + name: None, + })], + ), + ( + b"BEGIN TRANSACTION".as_slice(), + vec![Cmd::Stmt(Stmt::Begin { + typ: None, + name: None, + })], + ), + ( + b"BEGIN DEFERRED TRANSACTION".as_slice(), + vec![Cmd::Stmt(Stmt::Begin { + typ: Some(TransactionType::Deferred), + name: None, + })], + ), + ( + b"BEGIN IMMEDIATE TRANSACTION".as_slice(), + vec![Cmd::Stmt(Stmt::Begin { + typ: Some(TransactionType::Immediate), + name: None, + })], + ), + ( + b"BEGIN EXCLUSIVE TRANSACTION".as_slice(), + vec![Cmd::Stmt(Stmt::Begin { + typ: Some(TransactionType::Exclusive), + name: None, + })], + ), + ( + b"BEGIN DEFERRED TRANSACTION my_transaction".as_slice(), + vec![Cmd::Stmt(Stmt::Begin { + typ: Some(TransactionType::Deferred), + name: Some(Name::Ident("my_transaction".to_string())), + })], + ), + ( + b"BEGIN IMMEDIATE TRANSACTION my_transaction".as_slice(), + vec![Cmd::Stmt(Stmt::Begin { + typ: Some(TransactionType::Immediate), + name: Some(Name::Ident("my_transaction".to_string())), + })], + ), + ( + b"BEGIN EXCLUSIVE TRANSACTION my_transaction".as_slice(), + vec![Cmd::Stmt(Stmt::Begin { + typ: Some(TransactionType::Exclusive), + name: Some(Name::Ident("my_transaction".to_string())), + })], + ), + ( + b"BEGIN EXCLUSIVE TRANSACTION 'my_transaction'".as_slice(), + vec![Cmd::Stmt(Stmt::Begin { + typ: Some(TransactionType::Exclusive), + name: Some(Name::Quoted("'my_transaction'".to_string())), + })], + ), + ( + ";;;BEGIN;BEGIN;;;;;;BEGIN".as_bytes(), + vec![ + Cmd::Stmt(Stmt::Begin { + typ: None, + name: None, + }), + Cmd::Stmt(Stmt::Begin { + typ: None, + name: None, + }), + Cmd::Stmt(Stmt::Begin { + typ: None, + name: None, + }), + ], + ), + // commit + ( + b"COMMIT".as_slice(), + vec![Cmd::Stmt(Stmt::Commit { name: None })], + ), + ( + b"END".as_slice(), + vec![Cmd::Stmt(Stmt::Commit { name: None })], + ), + ( + b"COMMIT TRANSACTION".as_slice(), + vec![Cmd::Stmt(Stmt::Commit { name: None })], + ), + ( + b"END TRANSACTION".as_slice(), + vec![Cmd::Stmt(Stmt::Commit { name: None })], + ), + ( + b"COMMIT TRANSACTION my_transaction".as_slice(), + vec![Cmd::Stmt(Stmt::Commit { + name: Some(Name::Ident("my_transaction".to_string())), + })], + ), + ( + b"END TRANSACTION my_transaction".as_slice(), + vec![Cmd::Stmt(Stmt::Commit { + name: Some(Name::Ident("my_transaction".to_string())), + })], + ), + // Rollback + ( + b"ROLLBACK".as_slice(), + vec![Cmd::Stmt(Stmt::Rollback { + tx_name: None, + savepoint_name: None, + })], + ), + ( + b"ROLLBACK TO SAVEPOINT my_savepoint".as_slice(), + vec![Cmd::Stmt(Stmt::Rollback { + tx_name: None, + savepoint_name: Some(Name::Ident("my_savepoint".to_string())), + })], + ), + ( + b"ROLLBACK TO my_savepoint".as_slice(), + vec![Cmd::Stmt(Stmt::Rollback { + tx_name: None, + savepoint_name: Some(Name::Ident("my_savepoint".to_string())), + })], + ), + ( + b"ROLLBACK TRANSACTION my_transaction".as_slice(), + vec![Cmd::Stmt(Stmt::Rollback { + tx_name: Some(Name::Ident("my_transaction".to_string())), + savepoint_name: None, + })], + ), + ( + b"ROLLBACK TRANSACTION my_transaction TO my_savepoint".as_slice(), + vec![Cmd::Stmt(Stmt::Rollback { + tx_name: Some(Name::Ident("my_transaction".to_string())), + savepoint_name: Some(Name::Ident("my_savepoint".to_string())), + })], + ), + // savepoint + ( + b"SAVEPOINT my_savepoint".as_slice(), + vec![Cmd::Stmt(Stmt::Savepoint { + name: Name::Ident("my_savepoint".to_string()), + })], + ), + ( + b"SAVEPOINT 'my_savepoint'".as_slice(), + vec![Cmd::Stmt(Stmt::Savepoint { + name: Name::Quoted("'my_savepoint'".to_string()), + })], + ), + // release + ( + b"RELEASE my_savepoint".as_slice(), + vec![Cmd::Stmt(Stmt::Release { + name: Name::Ident("my_savepoint".to_string()), + })], + ), + ( + b"RELEASE SAVEPOINT my_savepoint".as_slice(), + vec![Cmd::Stmt(Stmt::Release { + name: Name::Ident("my_savepoint".to_string()), + })], + ), + ( + b"RELEASE SAVEPOINT 'my_savepoint'".as_slice(), + vec![Cmd::Stmt(Stmt::Release { + name: Name::Quoted("'my_savepoint'".to_string()), + })], + ), + ( + b"RELEASE SAVEPOINT ABORT".as_slice(), + vec![Cmd::Stmt(Stmt::Release { + name: Name::Ident("ABORT".to_string()), + })], + ), + // test expr operand + ( + b"SELECT 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT (1)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Parenthesized(vec![Box::new(Expr::Literal( + Literal::Numeric("1".to_owned()), + ))])), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT NULL".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Null)), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT X'ab'".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Blob("ab".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 3.333".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("3.333".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT ?1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Variable("1".to_owned())), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT CAST(1 AS INTEGER)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Cast { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + type_name: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT CAST(1 AS VARCHAR(255))".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Cast { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + type_name: Some(Type { + name: "VARCHAR".to_owned(), + size: Some(TypeSize::MaxSize(Box::new(Expr::Literal( + Literal::Numeric("255".to_owned()), + )))), + }), + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT CAST(1 AS DECIMAL(10, 5))".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Cast { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + type_name: Some(Type { + name: "DECIMAL".to_owned(), + size: Some(TypeSize::TypeSize( + Box::new(Expr::Literal(Literal::Numeric( + "10".to_owned(), + ))), + Box::new(Expr::Literal(Literal::Numeric( + "5".to_owned(), + ))), + )), + }), + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT CURRENT_DATE".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::CurrentDate)), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT CURRENT_TIME".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::CurrentTime)), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT CURRENT_TIMESTAMP".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::CurrentTimestamp)), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT NOT 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Unary( + UnaryOperator::Not, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT NOT 1 + 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Unary( + UnaryOperator::Not, + Box::new(Expr::Binary( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Add, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT ~1 + 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary( + Box::new(Expr::Unary( + UnaryOperator::BitwiseNot, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + Operator::Add, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT +1 + 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary( + Box::new(Expr::Unary( + UnaryOperator::Positive, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + Operator::Add, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT -1 + 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary( + Box::new(Expr::Unary( + UnaryOperator::Negative, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + Operator::Add, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT EXISTS (SELECT 1)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Exists(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric( + "1".to_owned(), + ))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + })), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT CASE WHEN 1 THEN 2 ELSE 3 END".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Case { + base: None, + when_then_pairs: vec![( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )], + else_expr: Some(Box::new(Expr::Literal(Literal::Numeric( + "3".to_owned(), + )))), + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT CASE 4 WHEN 1 THEN 2 ELSE 3 END".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Case { + base: Some(Box::new(Expr::Literal(Literal::Numeric( + "4".to_owned(), + )))), + when_then_pairs: vec![( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )], + else_expr: Some(Box::new(Expr::Literal(Literal::Numeric( + "3".to_owned(), + )))), + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT CASE 4 WHEN 1 THEN 2 END".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Case { + base: Some(Box::new(Expr::Literal(Literal::Numeric( + "4".to_owned(), + )))), + when_then_pairs: vec![( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )], + else_expr: None, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT (SELECT 1)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Subquery(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric( + "1".to_owned(), + ))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + })), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT RAISE (Ignore)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Raise(ResolveType::Ignore, None)), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT RAISE (FAIL, 'error')".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Raise( + ResolveType::Fail, + Some(Box::new(Expr::Literal(Literal::String( + "'error'".to_owned(), + )))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT RAISE (ROLLBACK, 'error')".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Raise( + ResolveType::Rollback, + Some(Box::new(Expr::Literal(Literal::String( + "'error'".to_owned(), + )))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT RAISE (ABORT, 'error')".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Raise( + ResolveType::Abort, + Some(Box::new(Expr::Literal(Literal::String( + "'error'".to_owned(), + )))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT RAISE (ABORT, 'error')".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Raise( + ResolveType::Abort, + Some(Box::new(Expr::Literal(Literal::String( + "'error'".to_owned(), + )))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT col_1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("col_1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 'col_1'".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::String("'col_1'".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT tbl_name.col_1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Qualified( + Name::Ident("tbl_name".to_owned()), + Name::Ident("col_1".to_owned()), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT schema_name.tbl_name.col_1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::DoublyQualified( + Name::Ident("schema_name".to_owned()), + Name::Ident("tbl_name".to_owned()), + Name::Ident("col_1".to_owned()), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name()".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: None, + args: vec![], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: None, + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) FILTER (WHERE x) OVER window_name".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: Some(Box::new(Expr::Id(Name::Ident( + "x".to_owned(), + )))), + over_clause: Some(Over::Name(Name::Ident( + "window_name".to_owned(), + ))), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (PARTITION BY product)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: None, + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: None, + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: None, + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product ORDER BY test ASC NULLS LAST)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![ + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("test".to_owned()))), + order: Some(SortOrder::Asc), + nulls: Some(NullsOrder::Last), + } + ], + frame_clause: None, + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Rows, + start: FrameBound::Preceding(Box::new(Expr::Literal(Literal::Numeric("2".to_owned())))), + end: Some(FrameBound::CurrentRow), + exclude: None + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product RANGE BETWEEN 2 PRECEDING AND CURRENT ROW)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Range, + start: FrameBound::Preceding(Box::new(Expr::Literal(Literal::Numeric("2".to_owned())))), + end: Some(FrameBound::CurrentRow), + exclude: None + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS BETWEEN 2 PRECEDING AND CURRENT ROW)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::Preceding(Box::new(Expr::Literal(Literal::Numeric("2".to_owned())))), + end: Some(FrameBound::CurrentRow), + exclude: None + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS BETWEEN 2 FOLLOWING AND CURRENT ROW)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::Following(Box::new(Expr::Literal(Literal::Numeric("2".to_owned())))), + end: Some(FrameBound::CurrentRow), + exclude: None + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::UnboundedPreceding, + end: Some(FrameBound::CurrentRow), + exclude: None + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS BETWEEN CURRENT ROW AND CURRENT ROW)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::CurrentRow, + end: Some(FrameBound::CurrentRow), + exclude: None + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::CurrentRow, + end: Some(FrameBound::UnboundedFollowing), + exclude: None + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS BETWEEN CURRENT ROW AND 1 PRECEDING)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::CurrentRow, + end: Some(FrameBound::Preceding( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))) + )), + exclude: None + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS BETWEEN CURRENT ROW AND 1 FOLLOWING)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::CurrentRow, + end: Some(FrameBound::Following( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))) + )), + exclude: None + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS CURRENT ROW EXCLUDE NO OTHERS)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::CurrentRow, + end: None, + exclude: Some(FrameExclude::NoOthers) + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS CURRENT ROW EXCLUDE CURRENT ROW)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::CurrentRow, + end: None, + exclude: Some(FrameExclude::CurrentRow) + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS CURRENT ROW EXCLUDE GROUP)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::CurrentRow, + end: None, + exclude: Some(FrameExclude::Group) + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT func_name(DISTINCT 1, 2) OVER (test PARTITION BY product GROUPS CURRENT ROW EXCLUDE TIES)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::FunctionCall { + name: Name::Ident("func_name".to_owned()), + distinctness: Some(Distinctness::Distinct), + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + order_by: vec![], + filter_over: FunctionTail { + filter_clause: None, + over_clause: Some(Over::Window(Window { + base: Some(Name::Ident("test".to_owned())), + partition_by: vec![Box::new(Expr::Id(Name::Ident( + "product".to_owned(), + )))], + order_by: vec![], + frame_clause: Some(FrameClause{ + mode: FrameMode::Groups, + start: FrameBound::CurrentRow, + end: None, + exclude: Some(FrameExclude::Ties) + }), + })), + }, + }), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + // parse expr + ( + b"SELECT 1 NOT NULL AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::NotNull( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 IS 1 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Is, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))) + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 IS NOT 1 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::IsNot, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))) + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 IS NOT DISTINCT FROM 1 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Is, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))) + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 IS DISTINCT FROM 1 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::IsNot, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))) + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 + 2 * 3".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Add, + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + Operator::Multiply, + Box::new(Expr::Literal(Literal::Numeric("3".to_owned()))) + )) + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 AND 2 OR 3".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))) + )), + Operator::Or, + Box::new(Expr::Literal(Literal::Numeric("3".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 = 0 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Equals, + Box::new(Expr::Literal(Literal::Numeric("0".to_owned()))) + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 != 0 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::NotEquals, + Box::new(Expr::Literal(Literal::Numeric("0".to_owned()))) + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 BETWEEN 2 AND 3 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Between { + lhs: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + not: false, + start: Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + end: Box::new(Expr::Literal(Literal::Numeric("3".to_owned()))), + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 NOT BETWEEN 2 AND 3 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Between { + lhs: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + not: true, + start: Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + end: Box::new(Expr::Literal(Literal::Numeric("3".to_owned()))), + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 IN (SELECT 1) AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::InSelect { + lhs: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + not: false, + rhs: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None + }, + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 NOT IN (SELECT 1) AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::InSelect { + lhs: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + not: true, + rhs: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None + }, + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 IN (1, 2, 3) AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::InList { + lhs: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + not: false, + rhs: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("3".to_owned()))), + ], + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 IN test(1, 2, 3) AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::InTable { + lhs: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + not: false, + rhs: QualifiedName { + db_name: None, + name: Name::Ident("test".to_owned()), + alias: None, + }, + args: vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("3".to_owned()))), + ], + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 'test' MATCH 'foo' AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Like { + lhs: Box::new(Expr::Literal(Literal::String("'test'".to_owned()))), + not: false, + op: LikeOperator::Match, + rhs: Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + escape: None, + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 'test' NOT MATCH 'foo' AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Like { + lhs: Box::new(Expr::Literal(Literal::String("'test'".to_owned()))), + not: true, + op: LikeOperator::Match, + rhs: Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + escape: None, + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 'test' NOT MATCH 'foo' ESCAPE 'bar' AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Like { + lhs: Box::new(Expr::Literal(Literal::String("'test'".to_owned()))), + not: true, + op: LikeOperator::Match, + rhs: Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + escape: Some(Box::new(Expr::Literal(Literal::String("'bar'".to_owned())))), + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 'test' NOT LIKE 'foo' ESCAPE 'bar' AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Like { + lhs: Box::new(Expr::Literal(Literal::String("'test'".to_owned()))), + not: true, + op: LikeOperator::Like, + rhs: Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + escape: Some(Box::new(Expr::Literal(Literal::String("'bar'".to_owned())))), + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 'test' NOT GLOB 'foo' ESCAPE 'bar' AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Like { + lhs: Box::new(Expr::Literal(Literal::String("'test'".to_owned()))), + not: true, + op: LikeOperator::Glob, + rhs: Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + escape: Some(Box::new(Expr::Literal(Literal::String("'bar'".to_owned())))), + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 'test' NOT REGEXP 'foo' ESCAPE 'bar' AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Like { + lhs: Box::new(Expr::Literal(Literal::String("'test'".to_owned()))), + not: true, + op: LikeOperator::Regexp, + rhs: Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + escape: Some(Box::new(Expr::Literal(Literal::String("'bar'".to_owned())))), + }), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 ISNULL AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::IsNull ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 NOTNULL AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::NotNull( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 < 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Less, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 > 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Greater, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 <= 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::LessEquals, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 >= 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::GreaterEquals, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 & 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::BitwiseAnd, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 | 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::BitwiseOr, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 << 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::LeftShift, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 >> 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::RightShift, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 / 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Divide, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 % 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Modulus, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 || 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Concat, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 -> 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::ArrowRight, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 ->> 2 AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Binary ( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::ArrowRightShift, + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 'foo' COLLATE bar AND 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Binary ( + Box::new(Expr::Collate ( + Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + Name::Ident("bar".to_owned()), + )), + Operator::And, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + // test select + ( + b"VALUES (1, 2), (3, 4), (5, 6)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + vec![ + Box::new(Expr::Literal(Literal::Numeric("3".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("4".to_owned()))), + ], + vec![ + Box::new(Expr::Literal(Literal::Numeric("5".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("6".to_owned()))), + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT *".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Star], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT tbl_name.*".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::TableStar( + Name::Ident("tbl_name".to_owned()), + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT col_1 OVER".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("col_1".to_owned()))), + Some(As::Elided(Name::Ident("OVER".to_owned()))), + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT col_1 AS OVER".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("col_1".to_owned()))), + Some(As::As(Name::Ident("OVER".to_owned()))), + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"WITH test AS (SELECT 1) SELECT 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: Some(With { + recursive: false, + ctes: vec![ + CommonTableExpr { + tbl_name: Name::Ident("test".to_owned()), + columns: vec![], + materialized: Materialized::Any, + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + } + }, + ] + }), + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"WITH test(col_1) AS MATERIALIZED (SELECT 1 AS col_1) SELECT 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: Some(With { + recursive: false, + ctes: vec![ + CommonTableExpr { + tbl_name: Name::Ident("test".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("col_1".to_owned()), + collation_name: None, + order: None, + }, + ], + materialized: Materialized::Yes, + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Some(As::As(Name::Ident("col_1".to_owned()))), + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + } + }, + ] + }), + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"WITH test(col_1) AS NOT MATERIALIZED (SELECT 1 AS col_1) SELECT 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: Some(With { + recursive: false, + ctes: vec![ + CommonTableExpr { + tbl_name: Name::Ident("test".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("col_1".to_owned()), + collation_name: None, + order: None, + }, + ], + materialized: Materialized::No, + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Some(As::As(Name::Ident("col_1".to_owned()))), + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + } + }, + ] + }), + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"WITH test AS (SELECT 1), test_2 AS (SELECT 1) SELECT 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: Some(With { + recursive: false, + ctes: vec![ + CommonTableExpr { + tbl_name: Name::Ident("test".to_owned()), + columns: vec![], + materialized: Materialized::Any, + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + } + }, + CommonTableExpr { + tbl_name: Name::Ident("test_2".to_owned()), + columns: vec![], + materialized: Materialized::Any, + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + } + }, + ] + }), + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 ORDER BY 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![ + SortedColumn { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + order: None, + nulls: None, + }, + ], + limit: None, + }))], + ), + ( + b"SELECT 1 ORDER BY 1 DESC NULLS FIRST".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![ + SortedColumn { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + order: Some(SortOrder::Desc), + nulls: Some(NullsOrder::First), + }, + ], + limit: None, + }))], + ), + ( + b"SELECT 1 ORDER BY 1 ASC NULLS LAST".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![ + SortedColumn { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + order: Some(SortOrder::Asc), + nulls: Some(NullsOrder::Last), + }, + ], + limit: None, + }))], + ), + ( + b"SELECT 1 LIMIT 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: Some(Limit { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + offset: None, + }), + }))], + ), + ( + b"SELECT 1 LIMIT 1,2".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: Some(Limit { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + offset: Some(Box::new(Expr::Literal(Literal::Numeric("2".to_owned())))), + }), + }))], + ), + ( + b"SELECT 1 LIMIT 1 OFFSET 2".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: Some(Limit { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + offset: Some(Box::new(Expr::Literal(Literal::Numeric("2".to_owned())))), + }), + }))], + ), + ( + b"SELECT 1 UNION SELECT 2".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![ + CompoundSelect { + operator: CompoundOperator::Union, + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + } + } + ], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 UNION ALL SELECT 2".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![ + CompoundSelect { + operator: CompoundOperator::UnionAll, + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + } + } + ], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 EXCEPT SELECT 2".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![ + CompoundSelect { + operator: CompoundOperator::Except, + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + } + } + ], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 INTERSECT SELECT 2".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![ + CompoundSelect { + operator: CompoundOperator::Intersect, + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + } + } + ], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo(1, 2)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::TableCall( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + None, + )), + joins: vec![] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM (SELECT 1)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Select( + Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + None, + )), + joins: vec![] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM (tbl_name)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Sub( + FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("tbl_name".to_owned()), alias: None }, + None, + None + )), + joins: vec![] + }, + None, + )), + joins: vec![] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo INDEXED BY bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + Some(Indexed::IndexedBy(Name::Ident("bar".to_owned()))), + )), + joins: vec![] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo NOT INDEXED".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + Some(Indexed::NotIndexed), + )), + joins: vec![] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo, bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::Comma, + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo, bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::Comma, + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo JOIN bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(None), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo NATURAL JOIN bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(Some(JoinType::NATURAL)), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo CROSS JOIN bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(Some(JoinType::INNER|JoinType::CROSS)), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo LEFT JOIN bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(Some(JoinType::LEFT|JoinType::OUTER)), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo RIGHT JOIN bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(Some(JoinType::RIGHT|JoinType::OUTER)), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo FULL JOIN bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(Some(JoinType::LEFT | JoinType::RIGHT | JoinType::OUTER)), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo INNER JOIN bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(Some(JoinType::INNER)), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo NATURAL INNER JOIN bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(Some(JoinType::NATURAL | JoinType::INNER)), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo NATURAL LEFT OUTER JOIN bar".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(Some(JoinType::NATURAL | JoinType::LEFT | JoinType::OUTER)), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo JOIN bar ON 1 = 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(None), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: Some(JoinConstraint::On(Box::new(Expr::Binary( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Equals, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )))), + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo JOIN bar USING (col_1)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(None), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + constraint: Some(JoinConstraint::Using(vec![ + Name::Ident("col_1".to_owned()), + ])), + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo JOIN bar bar_alias USING (col_1)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(None), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + Some(As::Elided(Name::Ident("bar_alias".to_owned()))), + None, + )), + constraint: Some(JoinConstraint::Using(vec![ + Name::Ident("col_1".to_owned()), + ])), + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo JOIN bar(1, 2)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(None), + table: Box::new(SelectTable::TableCall( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo JOIN (VALUES (1,2), (3, 4))".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(None), + table: Box::new(SelectTable::Select( + Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))) + ], + vec![ + Box::new(Expr::Literal(Literal::Numeric("3".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("4".to_owned()))) + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo JOIN (bar)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(None), + table: Box::new(SelectTable::Sub( + FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("bar".to_owned()), alias: None }, + None, + None, + )), + joins: vec![] + }, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo WHERE 1 = 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![] + }), + where_clause: Some(Box::new(Expr::Binary( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Equals, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + ))), + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo GROUP BY 1 = 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![] + }), + where_clause: None, + group_by: Some(GroupBy { + exprs: vec![ + Box::new(Expr::Binary( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Equals, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + ], + having: None, + }), + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo GROUP BY 1 = 1 HAVING 1 = 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![] + }), + where_clause: None, + group_by: Some(GroupBy { + exprs: vec![ + Box::new(Expr::Binary( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Equals, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + ], + having: Some(Box::new(Expr::Binary( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Equals, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + ))), + }), + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT 1 FROM foo GROUP BY 1 = 1 HAVING 1 = 1".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + None, + None, + )), + joins: vec![] + }), + where_clause: None, + group_by: Some(GroupBy { + exprs: vec![ + Box::new(Expr::Binary( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Equals, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )), + ], + having: Some(Box::new(Expr::Binary( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Operator::Equals, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + ))), + }), + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT * FROM t0 WINDOW JOIN t0;".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Star], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("t0".to_owned()), alias: None }, + Some(As::Elided(Name::Ident("WINDOW".to_owned()))), + None, + )), + joins: vec![ + JoinedSelectTable { + operator: JoinOperator::TypedJoin(None), + table: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("t0".to_owned()), alias: None }, + None, + None, + )), + constraint: None, + } + ] + }), + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT * FROM t0 WINDOW window_1 AS (PARTITION BY product)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Star], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("t0".to_owned()), alias: None }, + None, + None, + )), + joins: vec![] + }), + where_clause: None, + group_by: None, + window_clause: vec![ + WindowDef { + name: Name::Ident("window_1".to_owned()), + window: Window { + base: None, + partition_by: vec![ + Box::new(Expr::Id(Name::Ident("product".to_owned()))), + ], + order_by: vec![], + frame_clause: None, + }, + } + ], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + ( + b"SELECT * FROM t0 WINDOW window_1 AS (PARTITION BY product), window_2 AS (PARTITION BY product_2)".as_slice(), + vec![Cmd::Stmt(Stmt::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Star], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { db_name: None, name: Name::Ident("t0".to_owned()), alias: None }, + None, + None, + )), + joins: vec![] + }), + where_clause: None, + group_by: None, + window_clause: vec![ + WindowDef { + name: Name::Ident("window_1".to_owned()), + window: Window { + base: None, + partition_by: vec![ + Box::new(Expr::Id(Name::Ident("product".to_owned()))), + ], + order_by: vec![], + frame_clause: None, + }, + }, + WindowDef { + name: Name::Ident("window_2".to_owned()), + window: Window { + base: None, + partition_by: vec![ + Box::new(Expr::Id(Name::Ident("product_2".to_owned()))), + ], + order_by: vec![], + frame_clause: None, + }, + } + ], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }))], + ), + // parse Analyze + ( + b"ANALYZE".as_slice(), + vec![Cmd::Stmt(Stmt::Analyze { + name: None, + })], + ), + ( + b"ANALYZE foo".as_slice(), + vec![Cmd::Stmt(Stmt::Analyze { + name: Some(QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }), + })], + ), + // parse attach + ( + b"ATTACH DATABASE 'foo' AS bar".as_slice(), + vec![Cmd::Stmt(Stmt::Attach { + expr: Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + db_name: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + key: None, + })], + ), + ( + b"ATTACH 'foo' AS bar".as_slice(), + vec![Cmd::Stmt(Stmt::Attach { + expr: Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + db_name: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + key: None, + })], + ), + ( + b"ATTACH 'foo' AS bar key baz".as_slice(), + vec![Cmd::Stmt(Stmt::Attach { + expr: Box::new(Expr::Literal(Literal::String("'foo'".to_owned()))), + db_name: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + key: Some(Box::new(Expr::Id(Name::Ident("baz".to_owned())))), + })], + ), + // parse detach + ( + b"DETACH DATABASE bar".as_slice(), + vec![Cmd::Stmt(Stmt::Detach { + name: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + })], + ), + ( + b"DETACH bar".as_slice(), + vec![Cmd::Stmt(Stmt::Detach { + name: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + })], + ), + // parse pragma + ( + b"PRAGMA foreign_keys = ON".as_slice(), + vec![Cmd::Stmt(Stmt::Pragma { + name: QualifiedName { db_name: None, name: Name::Ident("foreign_keys".to_owned()), alias: None }, + body: Some(PragmaBody::Equals(Box::new(Expr::Literal(Literal::Keyword("ON".to_owned()))))), + })], + ), + ( + b"PRAGMA foreign_keys = DELETE".as_slice(), + vec![Cmd::Stmt(Stmt::Pragma { + name: QualifiedName { db_name: None, name: Name::Ident("foreign_keys".to_owned()), alias: None }, + body: Some(PragmaBody::Equals(Box::new(Expr::Literal(Literal::Keyword("DELETE".to_owned()))))), + })], + ), + ( + b"PRAGMA foreign_keys = DEFAULT".as_slice(), + vec![Cmd::Stmt(Stmt::Pragma { + name: QualifiedName { db_name: None, name: Name::Ident("foreign_keys".to_owned()), alias: None }, + body: Some(PragmaBody::Equals(Box::new(Expr::Literal(Literal::Keyword("DEFAULT".to_owned()))))), + })], + ), + ( + b"PRAGMA foreign_keys".as_slice(), + vec![Cmd::Stmt(Stmt::Pragma { + name: QualifiedName { db_name: None, name: Name::Ident("foreign_keys".to_owned()), alias: None }, + body: None, + })], + ), + ( + b"PRAGMA foreign_keys = 1".as_slice(), + vec![Cmd::Stmt(Stmt::Pragma { + name: QualifiedName { db_name: None, name: Name::Ident("foreign_keys".to_owned()), alias: None }, + body: Some(PragmaBody::Equals(Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))))), + })], + ), + ( + b"PRAGMA foreign_keys = test".as_slice(), + vec![Cmd::Stmt(Stmt::Pragma { + name: QualifiedName { db_name: None, name: Name::Ident("foreign_keys".to_owned()), alias: None }, + body: Some(PragmaBody::Equals(Box::new(Expr::Name(Name::Ident("test".to_owned()))))), + })], + ), + ( + b"PRAGMA foreign_keys".as_slice(), + vec![Cmd::Stmt(Stmt::Pragma { + name: QualifiedName { db_name: None, name: Name::Ident("foreign_keys".to_owned()), alias: None }, + body: None, + })], + ), + // parse vacuum + ( + b"VACUUM".as_slice(), + vec![Cmd::Stmt(Stmt::Vacuum { + name: None, + into: None, + })], + ), + ( + b"VACUUM INTO 'foo'".as_slice(), + vec![Cmd::Stmt(Stmt::Vacuum { + name: None, + into: Some(Box::new(Expr::Literal(Literal::String("'foo'".to_owned())))), + })], + ), + ( + b"VACUUM INTO foo".as_slice(), + vec![Cmd::Stmt(Stmt::Vacuum { + name: None, + into: Some(Box::new(Expr::Id(Name::Ident("foo".to_owned())))), + })], + ), + ( + b"VACUUM foo".as_slice(), + vec![Cmd::Stmt(Stmt::Vacuum { + name: Some(Name::Ident("foo".to_owned())), + into: None, + })], + ), + ( + b"VACUUM foo INTO 'bar'".as_slice(), + vec![Cmd::Stmt(Stmt::Vacuum { + name: Some(Name::Ident("foo".to_owned())), + into: Some(Box::new(Expr::Literal(Literal::String("'bar'".to_owned())))), + })], + ), + // parse alter + ( + b"ALTER TABLE foo RENAME TO bar".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::RenameTo(Name::Ident("bar".to_owned())), + })], + ), + ( + b"ALTER TABLE foo RENAME baz TO bar".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::RenameColumn { + old: Name::Ident("baz".to_owned()), + new: Name::Ident("bar".to_owned()) + }, + })], + ), + ( + b"ALTER TABLE foo RENAME COLUMN baz TO bar".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::RenameColumn { + old: Name::Ident("baz".to_owned()), + new: Name::Ident("bar".to_owned()) + }, + })], + ), + ( + b"ALTER TABLE foo DROP baz".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::DropColumn(Name::Ident("baz".to_owned())), + })], + ), + ( + b"ALTER TABLE foo DROP COLUMN baz".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::DropColumn(Name::Ident("baz".to_owned())), + })], + ), + ( + b"ALTER TABLE foo ADD baz".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: None, + constraints: vec![], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER DEFAULT 1".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Default( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))) + ), + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER DEFAULT (1)".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Default( + Box::new(Expr::Parenthesized(vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + ])) + ), + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER DEFAULT +1".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Default( + Box::new(Expr::Unary( + UnaryOperator::Positive, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )) + ), + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER DEFAULT -1".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Default( + Box::new(Expr::Unary( + UnaryOperator::Negative, + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + )) + ), + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER DEFAULT hello".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Default( + Box::new(Expr::Id(Name::Ident("hello".to_owned()))) + ), + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER NULL".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::NotNull { + nullable: true, + conflict_clause: None, + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER NOT NULL ON CONFLICT IGNORE".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::NotNull { + nullable: false, + conflict_clause: Some(ResolveType::Ignore), + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER NOT NULL ON CONFLICT REPLACE".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::NotNull { + nullable: false, + conflict_clause: Some(ResolveType::Replace), + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER NOT NULL ON CONFLICT ROLLBACK".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::NotNull { + nullable: false, + conflict_clause: Some(ResolveType::Rollback), + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER NOT NULL ON CONFLICT ROLLBACK".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::NotNull { + nullable: false, + conflict_clause: Some(ResolveType::Rollback), + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER PRIMARY KEY".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::PrimaryKey { + order: None, + conflict_clause: None, + auto_increment: false, + } + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER PRIMARY KEY ASC ON CONFLICT ROLLBACK AUTOINCREMENT".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::PrimaryKey { + order: Some(SortOrder::Asc), + conflict_clause: Some(ResolveType::Rollback), + auto_increment: true, + } + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER UNIQUE".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Unique(None), + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER UNIQUE ON CONFLICT ROLLBACK".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Unique(Some(ResolveType::Rollback)), + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER CHECK (1)".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Check( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + ), + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER CHECK (1)".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Check( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + ), + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![], + args: vec![] + }, + deref_clause: None + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar(test, test_2) MATCH test_3 ON INSERT SET NULL".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("test".to_owned()), + collation_name: None, + order: None, + }, + IndexedColumn { + col_name: Name::Ident("test_2".to_owned()), + collation_name: None, + order: None, + }, + ], + args: vec![ + RefArg::Match(Name::Ident("test_3".to_owned())), + RefArg::OnInsert(RefAct::SetNull), + ] + }, + deref_clause: None + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar(test, test_2) MATCH test_3 ON UPDATE SET NULL".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("test".to_owned()), + collation_name: None, + order: None, + }, + IndexedColumn { + col_name: Name::Ident("test_2".to_owned()), + collation_name: None, + order: None, + }, + ], + args: vec![ + RefArg::Match(Name::Ident("test_3".to_owned())), + RefArg::OnUpdate(RefAct::SetNull), + ] + }, + deref_clause: None + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar(test, test_2) MATCH test_3 ON DELETE SET NULL".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("test".to_owned()), + collation_name: None, + order: None, + }, + IndexedColumn { + col_name: Name::Ident("test_2".to_owned()), + collation_name: None, + order: None, + }, + ], + args: vec![ + RefArg::Match(Name::Ident("test_3".to_owned())), + RefArg::OnDelete(RefAct::SetNull), + ] + }, + deref_clause: None + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar(test, test_2) MATCH test_3 ON DELETE SET DEFAULT".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("test".to_owned()), + collation_name: None, + order: None, + }, + IndexedColumn { + col_name: Name::Ident("test_2".to_owned()), + collation_name: None, + order: None, + }, + ], + args: vec![ + RefArg::Match(Name::Ident("test_3".to_owned())), + RefArg::OnDelete(RefAct::SetDefault), + ] + }, + deref_clause: None + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar(test, test_2) MATCH test_3 ON DELETE CASCADE".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("test".to_owned()), + collation_name: None, + order: None, + }, + IndexedColumn { + col_name: Name::Ident("test_2".to_owned()), + collation_name: None, + order: None, + }, + ], + args: vec![ + RefArg::Match(Name::Ident("test_3".to_owned())), + RefArg::OnDelete(RefAct::Cascade), + ] + }, + deref_clause: None + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar(test, test_2) MATCH test_3 ON DELETE RESTRICT".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("test".to_owned()), + collation_name: None, + order: None, + }, + IndexedColumn { + col_name: Name::Ident("test_2".to_owned()), + collation_name: None, + order: None, + }, + ], + args: vec![ + RefArg::Match(Name::Ident("test_3".to_owned())), + RefArg::OnDelete(RefAct::Restrict), + ] + }, + deref_clause: None + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar(test, test_2) MATCH test_3 ON DELETE NO ACTION".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("test".to_owned()), + collation_name: None, + order: None, + }, + IndexedColumn { + col_name: Name::Ident("test_2".to_owned()), + collation_name: None, + order: None, + }, + ], + args: vec![ + RefArg::Match(Name::Ident("test_3".to_owned())), + RefArg::OnDelete(RefAct::NoAction), + ] + }, + deref_clause: None + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar DEFERRABLE".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![], + args: vec![] + }, + deref_clause: Some(DeferSubclause { + deferrable: true, + init_deferred: None, + }) + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar NOT DEFERRABLE INITIALLY IMMEDIATE".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![], + args: vec![] + }, + deref_clause: Some(DeferSubclause { + deferrable: false, + init_deferred: Some(InitDeferredPred::InitiallyImmediate), + }) + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar NOT DEFERRABLE INITIALLY DEFERRED".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![], + args: vec![] + }, + deref_clause: Some(DeferSubclause { + deferrable: false, + init_deferred: Some(InitDeferredPred::InitiallyDeferred), + }) + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER REFERENCES bar NOT DEFERRABLE INITIALLY DEFERRED".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::ForeignKey { + clause: ForeignKeyClause { + tbl_name: Name::Ident("bar".to_owned()), + columns: vec![], + args: vec![] + }, + deref_clause: Some(DeferSubclause { + deferrable: false, + init_deferred: Some(InitDeferredPred::InitiallyDeferred), + }) + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER COLLATE bar".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Collate { + collation_name: Name::Ident("bar".to_owned()), + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER GENERATED ALWAYS AS (1)".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Generated { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + typ: None, + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER AS (1)".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Generated { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + typ: None, + }, + }, + ], + }), + })], + ), + ( + b"ALTER TABLE foo ADD COLUMN baz INTEGER AS (1) STORED".as_slice(), + vec![Cmd::Stmt(Stmt::AlterTable { + name: QualifiedName { db_name: None, name: Name::Ident("foo".to_owned()), alias: None }, + body: AlterTableBody::AddColumn(ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![ + NamedColumnConstraint { + name: None, + constraint: ColumnConstraint::Generated { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + typ: Some(Name::Ident("STORED".to_owned())), + }, + }, + ], + }), + })], + ), + // parse create index + ( + b"CREATE INDEX idx_foo ON foo (bar)".as_slice(), + vec![Cmd::Stmt(Stmt::CreateIndex { + unique: false, + if_not_exists: false, + idx_name: QualifiedName { + db_name: None, + name: Name::Ident("idx_foo".to_owned()), + alias: None, + }, + tbl_name: Name::Ident("foo".to_owned()), + columns: vec![SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None, + }], + where_clause: None, + })], + ), + ( + b"CREATE UNIQUE INDEX IF NOT EXISTS idx_foo ON foo (bar) WHERE 1".as_slice(), + vec![Cmd::Stmt(Stmt::CreateIndex { + unique: true, + if_not_exists: true, + idx_name: QualifiedName { + db_name: None, + name: Name::Ident("idx_foo".to_owned()), + alias: None, + }, + tbl_name: Name::Ident("foo".to_owned()), + columns: vec![SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None, + }], + where_clause: Some(Box::new( + Expr::Literal(Literal::Numeric("1".to_owned())) + )), + })], + ), + // parse create table + ( + b"CREATE TABLE foo (column)".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: false, + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("column".to_owned()), + col_type: None, + constraints: vec![], + }, + ], + constraints: vec![], + options: TableOptions::NONE, + }, + })], + ), + ( + b"CREATE TABLE foo AS SELECT 1".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: false, + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::AsSelect(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ + ResultColumn::Expr(Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), None) + ], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![] + }, + order_by: vec![], + limit: None, + }), + })], + ), + ( + b"CREATE TEMP TABLE IF NOT EXISTS foo (bar, baz INTEGER, CONSTRAINT tbl_cons PRIMARY KEY (bar AUTOINCREMENT) ON CONFLICT ROLLBACK) STRICT".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: true, + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("bar".to_owned()), + col_type: None, + constraints: vec![], + }, + ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![], + }, + ], + constraints: vec![ + NamedTableConstraint { + name: Some(Name::Ident("tbl_cons".to_owned())), + constraint: TableConstraint::PrimaryKey { + columns: vec![ + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None, + }, + ], + auto_increment: true, + conflict_clause: Some(ResolveType::Rollback) + } + }, + ], + options: TableOptions::STRICT, + }, + })], + ), + ( + b"CREATE TEMP TABLE IF NOT EXISTS foo (bar, baz INTEGER, UNIQUE (bar) ON CONFLICT ROLLBACK) WITHOUT ROWID".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: true, + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("bar".to_owned()), + col_type: None, + constraints: vec![], + }, + ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![], + }, + ], + constraints: vec![ + NamedTableConstraint { + name: None, + constraint: TableConstraint::Unique { + columns: vec![ + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None, + }, + ], + conflict_clause: Some(ResolveType::Rollback) + } + }, + ], + options: TableOptions::WITHOUT_ROWID, + }, + })], + ), + ( + b"CREATE TEMP TABLE IF NOT EXISTS foo (bar, baz INTEGER, CHECK (1))".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: true, + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("bar".to_owned()), + col_type: None, + constraints: vec![], + }, + ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![], + }, + ], + constraints: vec![ + NamedTableConstraint { + name: None, + constraint: TableConstraint::Check(Box::new( + Expr::Literal(Literal::Numeric("1".to_owned())) + )), + }, + ], + options: TableOptions::NONE, + }, + })], + ), + ( + b"CREATE TEMP TABLE IF NOT EXISTS foo (bar, baz INTEGER, FOREIGN KEY (bar) REFERENCES foo_2(bar_2), CHECK (1))".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: true, + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("bar".to_owned()), + col_type: None, + constraints: vec![], + }, + ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![], + }, + ], + constraints: vec![ + NamedTableConstraint { + name: None, + constraint: TableConstraint::ForeignKey { + columns: vec![ + IndexedColumn { + col_name: Name::Ident("bar".to_owned()), + collation_name: None, + order: None, + }, + ], + clause: ForeignKeyClause { + tbl_name: Name::Ident("foo_2".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("bar_2".to_owned()), + collation_name: None, + order: None, + }, + ], + args: vec![], + }, + deref_clause: None, + }, + }, + NamedTableConstraint { + name: None, + constraint: TableConstraint::Check(Box::new( + Expr::Literal(Literal::Numeric("1".to_owned())) + )), + }, + ], + options: TableOptions::NONE, + }, + })], + ), + // parse create trigger + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN SELECT 1; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }) + ], + })], + ), + ( + b"CREATE TEMP TRIGGER IF NOT EXISTS foo AFTER UPDATE ON bar FOR EACH ROW WHEN 1 BEGIN SELECT 1; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: true, + if_not_exists: true, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: Some(TriggerTime::After), + event: TriggerEvent::Update, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: true, + when_clause: Some(Box::new(Expr::Literal(Literal::Numeric("1".to_owned())))), + commands: vec![ + TriggerCmd::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }) + ], + })], + ), + ( + b"CREATE TEMP TRIGGER IF NOT EXISTS foo BEFORE DELETE ON bar FOR EACH ROW WHEN 1 BEGIN SELECT 1; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: true, + if_not_exists: true, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: Some(TriggerTime::Before), + event: TriggerEvent::Delete, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: true, + when_clause: Some(Box::new(Expr::Literal(Literal::Numeric("1".to_owned())))), + commands: vec![ + TriggerCmd::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }) + ], + })], + ), + ( + b"CREATE TEMP TRIGGER IF NOT EXISTS foo INSTEAD OF UPDATE OF baz, bar ON bar FOR EACH ROW WHEN 1 BEGIN SELECT 1; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: true, + if_not_exists: true, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: Some(TriggerTime::InsteadOf), + event: TriggerEvent::UpdateOf(vec![ + Name::Ident("baz".to_owned()), + Name::Ident("bar".to_owned()), + ]), + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: true, + when_clause: Some(Box::new(Expr::Literal(Literal::Numeric("1".to_owned())))), + commands: vec![ + TriggerCmd::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }) + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN INSERT INTO foo VALUES (1, 2); END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Insert { + or_conflict: None, + tbl_name: Name::Ident("foo".to_owned()), + col_names: vec![], + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + upsert: None, + returning: vec![], + }, + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN INSERT OR ROLLBACK INTO foo(bar, baz) VALUES (1, 2); END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Insert { + or_conflict: Some(ResolveType::Rollback), + tbl_name: Name::Ident("foo".to_owned()), + col_names: vec![ + Name::Ident("bar".to_owned()), + Name::Ident("baz".to_owned()), + ], + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + upsert: None, + returning: vec![], + }, + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN INSERT INTO foo VALUES (1, 2) RETURNING bar, baz; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Insert { + or_conflict: None, + tbl_name: Name::Ident("foo".to_owned()), + col_names: vec![], + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + upsert: None, + returning: vec![ + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + None, + ), + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("baz".to_owned()))), + None, + ), + ], + }, + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN INSERT INTO foo VALUES (1, 2) ON CONFLICT (bar, baz) WHERE 1 DO NOTHING RETURNING bar, baz; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Insert { + or_conflict: None, + tbl_name: Name::Ident("foo".to_owned()), + col_names: vec![], + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + upsert: Some(Box::new(Upsert { + index: Some(UpsertIndex { + targets: vec![ + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None + }, + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("baz".to_owned()))), + order: None, + nulls: None + }, + ], + where_clause: Some(Box::new(Expr::Literal(Literal::Numeric("1".to_owned())))), + }), + do_clause: UpsertDo::Nothing, + next: None + })), + returning: vec![ + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + None, + ), + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("baz".to_owned()))), + None, + ), + ], + }, + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN INSERT INTO foo VALUES (1, 2) ON CONFLICT DO UPDATE SET (bar, baz) = 1 WHERE 1 RETURNING bar, baz; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Insert { + or_conflict: None, + tbl_name: Name::Ident("foo".to_owned()), + col_names: vec![], + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + upsert: Some(Box::new(Upsert { + index: None, + do_clause: UpsertDo::Set { + sets: vec![ + Set { + col_names: vec![ + Name::Ident("bar".to_owned()), + Name::Ident("baz".to_owned()), + ], + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + } + ], + where_clause: Some(Box::new(Expr::Literal(Literal::Numeric("1".to_owned())))), + }, + next: None + })), + returning: vec![ + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + None, + ), + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("baz".to_owned()))), + None, + ), + ], + }, + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN INSERT INTO foo VALUES (1, 2) ON CONFLICT (bar, baz) DO NOTHING ON CONFLICT DO NOTHING; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Insert { + or_conflict: None, + tbl_name: Name::Ident("foo".to_owned()), + col_names: vec![], + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + upsert: Some(Box::new(Upsert { + index: Some(UpsertIndex { + targets: vec![ + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None + }, + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("baz".to_owned()))), + order: None, + nulls: None + }, + ], + where_clause: None, + }), + do_clause: UpsertDo::Nothing, + next: Some(Box::new(Upsert { + index: None, + do_clause: UpsertDo::Nothing, + next: None, + })), + })), + returning: vec![], + }, + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN UPDATE foo SET bar = 1; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Update { + or_conflict: None, + tbl_name: Name::Ident("foo".to_owned()), + sets: vec![ + Set { + col_names: vec![Name::Ident("bar".to_owned())], + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + }, + ], + from: None, + where_clause: None, + }, + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN DELETE FROM foo; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Delete { + tbl_name: Name::Ident("foo".to_owned()), + where_clause: None, + }, + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN DELETE FROM foo WHERE 1; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Delete { + tbl_name: Name::Ident("foo".to_owned()), + where_clause: Some(Box::new(Expr::Literal(Literal::Numeric("1".to_owned())))), + }, + ], + })], + ), + ( + b"CREATE TRIGGER foo INSERT ON bar BEGIN SELECT 1; SELECT 1; END".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTrigger { + temporary: false, + if_not_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + time: None, + event: TriggerEvent::Insert, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("bar".to_owned()), + alias: None, + }, + for_each_row: false, + when_clause: None, + commands: vec![ + TriggerCmd::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }), + TriggerCmd::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }) + ], + })], + ), + // parse create view + ( + b"CREATE VIEW foo AS SELECT 1".as_slice(), + vec![Cmd::Stmt(Stmt::CreateView { + temporary: false, + if_not_exists: false, + view_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + columns: vec![], + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + })], + ), + ( + b"CREATE TEMP VIEW IF NOT EXISTS foo(bar) AS SELECT 1".as_slice(), + vec![Cmd::Stmt(Stmt::CreateView { + temporary: true, + if_not_exists: true, + view_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + columns: vec![ + IndexedColumn { + col_name: Name::Ident("bar".to_owned()), + collation_name: None, + order: None, + } + ], + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + })], + ), + // parse CREATE VIRTUAL TABLE + ( + b"CREATE VIRTUAL TABLE foo USING bar".as_slice(), + vec![Cmd::Stmt(Stmt::CreateVirtualTable { + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + module_name: Name::Ident("bar".to_owned()), + args: vec![], + })], + ), + ( + b"CREATE VIRTUAL TABLE foo USING bar()".as_slice(), + vec![Cmd::Stmt(Stmt::CreateVirtualTable { + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + module_name: Name::Ident("bar".to_owned()), + args: vec![], + })], + ), + ( + b"CREATE VIRTUAL TABLE IF NOT EXISTS foo USING bar(1, 2, ('hello', (3.333), 'world', (1, 2)))".as_slice(), + vec![Cmd::Stmt(Stmt::CreateVirtualTable { + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + module_name: Name::Ident("bar".to_owned()), + args: vec![ + "1".to_owned(), + "2".to_owned(), + "('hello', (3.333), 'world', (1, 2))".to_owned(), + ], + })], + ), + ( + b"CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = '''porter'' ''ascii''')".as_slice(), + vec![Cmd::Stmt(Stmt::CreateVirtualTable { + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("ft".to_owned()), + alias: None, + }, + module_name: Name::Ident("fts5".to_owned()), + args: vec![ + "x".to_owned(), + "tokenize = '''porter'' ''ascii'''".to_owned(), + ], + })], + ), + // parse delete + ( + b"DELETE FROM foo".as_slice(), + vec![Cmd::Stmt(Stmt::Delete { + with: None, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None + }, + indexed: None, + where_clause: None, + returning: vec![], + order_by: vec![], + limit: None, + })], + ), + ( + b"WITH test AS (SELECT 1) DELETE FROM foo NOT INDEXED WHERE 1 RETURNING bar ORDER BY bar LIMIT 1".as_slice(), + vec![Cmd::Stmt(Stmt::Delete { + with: Some(With { + recursive: false, + ctes: vec![ + CommonTableExpr { + tbl_name: Name::Ident("test".to_owned()), + columns: vec![], + materialized: Materialized::Any, + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + } + ], + }), + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None + }, + indexed: Some(Indexed::NotIndexed), + where_clause: Some(Box::new(Expr::Literal(Literal::Numeric("1".to_owned())))), + returning: vec![ + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + None, + ), + ], + order_by: vec![ + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None, + } + ], + limit: Some(Limit { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + offset: None, + }), + })], + ), + // parse drop index + ( + b"DROP INDEX foo".as_slice(), + vec![Cmd::Stmt(Stmt::DropIndex { + if_exists: false, + idx_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + })], + ), + ( + b"DROP INDEX IF EXISTS foo".as_slice(), + vec![Cmd::Stmt(Stmt::DropIndex { + if_exists: true, + idx_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + })], + ), + // parse drop table + ( + b"DROP TABLE foo".as_slice(), + vec![Cmd::Stmt(Stmt::DropTable { + if_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + })], + ), + ( + b"DROP TABLE IF EXISTS foo".as_slice(), + vec![Cmd::Stmt(Stmt::DropTable { + if_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + })], + ), + // parse drop trigger + ( + b"DROP TRIGGER foo".as_slice(), + vec![Cmd::Stmt(Stmt::DropTrigger { + if_exists: false, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + })], + ), + ( + b"DROP TRIGGER IF EXISTS foo".as_slice(), + vec![Cmd::Stmt(Stmt::DropTrigger { + if_exists: true, + trigger_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + })], + ), + // parse drop view + ( + b"DROP VIEW foo".as_slice(), + vec![Cmd::Stmt(Stmt::DropView { + if_exists: false, + view_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + })], + ), + ( + b"DROP VIEW IF EXISTS foo".as_slice(), + vec![Cmd::Stmt(Stmt::DropView { + if_exists: true, + view_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + })], + ), + // parse insert + ( + b"INSERT INTO foo VALUES (1, 2)".as_slice(), + vec![Cmd::Stmt(Stmt::Insert { + with: None, + or_conflict: None, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + columns: vec![], + body: InsertBody::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, None), + returning: vec![], + })], + ), + ( + b"REPLACE INTO foo VALUES (1, 2)".as_slice(), + vec![Cmd::Stmt(Stmt::Insert { + with: None, + or_conflict: Some(ResolveType::Replace), + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + columns: vec![], + body: InsertBody::Select(Select { + with: None, + body: SelectBody { + select: OneSelect::Values(vec![ + vec![ + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + Box::new(Expr::Literal(Literal::Numeric("2".to_owned()))), + ], + ]), + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, None), + returning: vec![], + })], + ), + ( + b"WITH test AS (SELECT 1) INSERT INTO foo(bar, baz) DEFAULT VALUES RETURNING bar".as_slice(), + vec![Cmd::Stmt(Stmt::Insert { + with: Some(With { + recursive: false, + ctes: vec![ + CommonTableExpr { + tbl_name: Name::Ident("test".to_owned()), + columns: vec![], + materialized: Materialized::Any, + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + } + ], + }), + or_conflict: None, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + columns: vec![ + Name::Ident("bar".to_owned()), + Name::Ident("baz".to_owned()), + ], + body: InsertBody::DefaultValues, + returning: vec![ + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + None, + ), + ], + })], + ), + ( + b"WITH test AS (SELECT 1) REPLACE INTO foo(bar, baz) DEFAULT VALUES RETURNING bar".as_slice(), + vec![Cmd::Stmt(Stmt::Insert { + with: Some(With { + recursive: false, + ctes: vec![ + CommonTableExpr { + tbl_name: Name::Ident("test".to_owned()), + columns: vec![], + materialized: Materialized::Any, + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + } + ], + }), + or_conflict: Some(ResolveType::Replace), + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + columns: vec![ + Name::Ident("bar".to_owned()), + Name::Ident("baz".to_owned()), + ], + body: InsertBody::DefaultValues, + returning: vec![ + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + None, + ), + ], + })], + ), + // parse update + ( + b"UPDATE foo SET bar = 1".as_slice(), + vec![Cmd::Stmt(Stmt::Update { + with: None, + or_conflict: None, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + indexed: None, + sets: vec![ + Set { + col_names: vec![ + Name::Ident("bar".to_owned()), + ], + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + } + ], + from: None, + where_clause: None, + returning: vec![], + order_by: vec![], + limit: None, + })], + ), + ( + b"WITH test AS (SELECT 1) UPDATE OR REPLACE foo NOT INDEXED SET bar = 1 FROM foo_2 WHERE 1 RETURNING bar ORDER By bar LIMIT 1".as_slice(), + vec![Cmd::Stmt(Stmt::Update { + with: Some(With { + recursive: false, + ctes: vec![ + CommonTableExpr { + tbl_name: Name::Ident("test".to_owned()), + columns: vec![], + materialized: Materialized::Any, + select: Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ResultColumn::Expr( + Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + None, + )], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![], + }, + order_by: vec![], + limit: None, + }, + } + ], + }), + or_conflict: Some(ResolveType::Replace), + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + indexed: Some(Indexed::NotIndexed), + sets: vec![ + Set { + col_names: vec![ + Name::Ident("bar".to_owned()), + ], + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + } + ], + from: Some(FromClause { + select: Box::new(SelectTable::Table( + QualifiedName { + db_name: None, + name: Name::Ident("foo_2".to_owned()), + alias: None, + }, + None, + None, + )), + joins: vec![] + }), + where_clause: Some(Box::new(Expr::Literal(Literal::Numeric("1".to_owned())))), + returning: vec![ + ResultColumn::Expr( + Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + None, + ), + ], + order_by: vec![ + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None, + } + ], + limit: Some(Limit { + expr: Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), + offset: None, + }), + })], + ), + // parse reindex + ( + b"REINDEX".as_slice(), + vec![Cmd::Stmt(Stmt::Reindex { + name: None, + })], + ), + ( + b"REINDEX foo".as_slice(), + vec![Cmd::Stmt(Stmt::Reindex { + name: Some(QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None + }), + })], + ), + ]; + + for (input, expected) in test_cases { + println!("Testing input: {:?}", from_bytes(input)); + let parser = Parser::new(input); + let mut results = Vec::new(); + for cmd in parser { + results.push(cmd.unwrap()); + } + + assert_eq!(results, expected, "Input: {input:?}"); + } + } +} diff --git a/parser/src/token.rs b/parser/src/token.rs new file mode 100644 index 000000000..c838a1719 --- /dev/null +++ b/parser/src/token.rs @@ -0,0 +1,384 @@ +use std::fmt::Display; + +/// Token classes +// Generated by lemon (parse.h). +// Renamed manually. +// To be keep in sync. +#[non_exhaustive] +#[allow(non_camel_case_types, missing_docs)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)] +#[repr(u16)] +pub enum TokenType { + TK_EOF = 0, + TK_SEMI = 1, + TK_EXPLAIN = 2, + TK_QUERY = 3, + TK_PLAN = 4, + TK_BEGIN = 5, + TK_TRANSACTION = 6, + TK_DEFERRED = 7, + TK_IMMEDIATE = 8, + TK_EXCLUSIVE = 9, + TK_COMMIT = 10, + TK_END = 11, + TK_ROLLBACK = 12, + TK_SAVEPOINT = 13, + TK_RELEASE = 14, + TK_TO = 15, + TK_TABLE = 16, + TK_CREATE = 17, + TK_IF = 18, + TK_NOT = 19, + TK_EXISTS = 20, + TK_TEMP = 21, + TK_LP = 22, + TK_RP = 23, + TK_AS = 24, + TK_COMMA = 25, + TK_WITHOUT = 26, + TK_ABORT = 27, + TK_ACTION = 28, + TK_AFTER = 29, + TK_ANALYZE = 30, + TK_ASC = 31, + TK_ATTACH = 32, + TK_BEFORE = 33, + TK_BY = 34, + TK_CASCADE = 35, + TK_CAST = 36, + TK_CONFLICT = 37, + TK_DATABASE = 38, + TK_DESC = 39, + TK_DETACH = 40, + TK_EACH = 41, + TK_FAIL = 42, + TK_OR = 43, + TK_AND = 44, + TK_IS = 45, + TK_ISNOT = 46, + TK_MATCH = 47, + TK_LIKE_KW = 48, + TK_BETWEEN = 49, + TK_IN = 50, + TK_ISNULL = 51, + TK_NOTNULL = 52, + TK_NE = 53, + TK_EQ = 54, + TK_GT = 55, + TK_LE = 56, + TK_LT = 57, + TK_GE = 58, + TK_ESCAPE = 59, + TK_ID = 60, + TK_COLUMNKW = 61, + TK_DO = 62, + TK_FOR = 63, + TK_IGNORE = 64, + TK_INITIALLY = 65, + TK_INSTEAD = 66, + TK_NO = 67, + TK_KEY = 68, + TK_OF = 69, + TK_OFFSET = 70, + TK_PRAGMA = 71, + TK_RAISE = 72, + TK_RECURSIVE = 73, + TK_REPLACE = 74, + TK_RESTRICT = 75, + TK_ROW = 76, + TK_ROWS = 77, + TK_TRIGGER = 78, + TK_VACUUM = 79, + TK_VIEW = 80, + TK_VIRTUAL = 81, + TK_WITH = 82, + TK_NULLS = 83, + TK_FIRST = 84, + TK_LAST = 85, + TK_CURRENT = 86, + TK_FOLLOWING = 87, + TK_PARTITION = 88, + TK_PRECEDING = 89, + TK_RANGE = 90, + TK_UNBOUNDED = 91, + TK_EXCLUDE = 92, + TK_GROUPS = 93, + TK_OTHERS = 94, + TK_TIES = 95, + TK_GENERATED = 96, + TK_ALWAYS = 97, + TK_MATERIALIZED = 98, + TK_REINDEX = 99, + TK_RENAME = 100, + TK_CTIME_KW = 101, + TK_ANY = 102, + TK_BITAND = 103, + TK_BITOR = 104, + TK_LSHIFT = 105, + TK_RSHIFT = 106, + TK_PLUS = 107, + TK_MINUS = 108, + TK_STAR = 109, + TK_SLASH = 110, + TK_REM = 111, + TK_CONCAT = 112, + TK_PTR = 113, + TK_COLLATE = 114, + TK_BITNOT = 115, + TK_ON = 116, + TK_INDEXED = 117, + TK_STRING = 118, + TK_JOIN_KW = 119, + TK_CONSTRAINT = 120, + TK_DEFAULT = 121, + TK_NULL = 122, + TK_PRIMARY = 123, + TK_UNIQUE = 124, + TK_CHECK = 125, + TK_REFERENCES = 126, + TK_AUTOINCR = 127, + TK_INSERT = 128, + TK_DELETE = 129, + TK_UPDATE = 130, + TK_SET = 131, + TK_DEFERRABLE = 132, + TK_FOREIGN = 133, + TK_DROP = 134, + TK_UNION = 135, + TK_ALL = 136, + TK_EXCEPT = 137, + TK_INTERSECT = 138, + TK_SELECT = 139, + TK_VALUES = 140, + TK_DISTINCT = 141, + TK_DOT = 142, + TK_FROM = 143, + TK_JOIN = 144, + TK_USING = 145, + TK_ORDER = 146, + TK_GROUP = 147, + TK_HAVING = 148, + TK_LIMIT = 149, + TK_WHERE = 150, + TK_RETURNING = 151, + TK_INTO = 152, + TK_NOTHING = 153, + TK_BLOB = 154, + TK_FLOAT = 155, + TK_INTEGER = 156, + TK_VARIABLE = 157, + TK_CASE = 158, + TK_WHEN = 159, + TK_THEN = 160, + TK_ELSE = 161, + TK_INDEX = 162, + TK_ALTER = 163, + TK_ADD = 164, + TK_WINDOW = 165, + TK_OVER = 166, + TK_FILTER = 167, + TK_ILLEGAL = 185, +} + +impl Display for TokenType { + // for debugging purposes + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use TokenType::*; + let s = match self { + TK_EOF => "TK_EOF", + TK_SEMI => "TK_SEMI", + TK_EXPLAIN => "TK_EXPLAIN", + TK_QUERY => "TK_QUERY", + TK_PLAN => "TK_PLAN", + TK_BEGIN => "TK_BEGIN", + TK_TRANSACTION => "TK_TRANSACTION", + TK_DEFERRED => "TK_DEFERRED", + TK_IMMEDIATE => "TK_IMMEDIATE", + TK_EXCLUSIVE => "TK_EXCLUSIVE", + TK_COMMIT => "TK_COMMIT", + TK_END => "TK_END", + TK_ROLLBACK => "TK_ROLLBACK", + TK_SAVEPOINT => "TK_SAVEPOINT", + TK_RELEASE => "TK_RELEASE", + TK_TO => "TK_TO", + TK_TABLE => "TK_TABLE", + TK_CREATE => "TK_CREATE", + TK_IF => "TK_IF", + TK_NOT => "TK_NOT", + TK_EXISTS => "TK_EXISTS", + TK_TEMP => "TK_TEMP", + TK_LP => "TK_LP", + TK_RP => "TK_RP", + TK_AS => "TK_AS", + TK_COMMA => "TK_COMMA", + TK_WITHOUT => "TK_WITHOUT", + TK_ABORT => "TK_ABORT", + TK_ACTION => "TK_ACTION", + TK_AFTER => "TK_AFTER", + TK_ANALYZE => "TK_ANALYZE", + TK_ASC => "TK_ASC", + TK_ATTACH => "TK_ATTACH", + TK_BEFORE => "TK_BEFORE", + TK_BY => "TK_BY", + TK_CASCADE => "TK_CASCADE", + TK_CAST => "TK_CAST", + TK_CONFLICT => "TK_CONFLICT", + TK_DATABASE => "TK_DATABASE", + TK_DESC => "TK_DESC", + TK_DETACH => "TK_DETACH", + TK_EACH => "TK_EACH", + TK_FAIL => "TK_FAIL", + TK_OR => "TK_OR", + TK_AND => "TK_AND", + TK_IS => "TK_IS", + TK_ISNOT => "TK_ISNOT", + TK_MATCH => "TK_MATCH", + TK_LIKE_KW => "TK_LIKE_KW", + TK_BETWEEN => "TK_BETWEEN", + TK_IN => "TK_IN", + TK_ISNULL => "TK_ISNULL", + TK_NOTNULL => "TK_NOTNULL", + TK_NE => "TK_NE", + TK_EQ => "TK_EQ", + TK_GT => "TK_GT", + TK_LE => "TK_LE", + TK_LT => "TK_LT", + TK_GE => "TK_GE", + TK_ESCAPE => "TK_ESCAPE", + TK_ID => "TK_ID", + TK_COLUMNKW => "TK_COLUMNKW", + TK_DO => "TK_DO", + TK_FOR => "TK_FOR", + TK_IGNORE => "TK_IGNORE", + TK_INITIALLY => "TK_INITIALLY", + TK_INSTEAD => "TK_INSTEAD", + TK_NO => "TK_NO", + TK_KEY => "TK_KEY", + TK_OF => "TK_OF", + TK_OFFSET => "TK_OFFSET", + TK_PRAGMA => "TK_PRAGMA", + TK_RAISE => "TK_RAISE", + TK_RECURSIVE => "TK_RECURSIVE", + TK_REPLACE => "TK_REPLACE", + TK_RESTRICT => "TK_RESTRICT", + TK_ROW => "TK_ROW", + TK_ROWS => "TK_ROWS", + TK_TRIGGER => "TK_TRIGGER", + TK_VACUUM => "TK_VACUUM", + TK_VIEW => "TK_VIEW", + TK_VIRTUAL => "TK_VIRTUAL", + TK_WITH => "TK_WITH", + TK_NULLS => "TK_NULLS", + TK_FIRST => "TK_FIRST", + TK_LAST => "TK_LAST", + TK_CURRENT => "TK_CURRENT", + TK_FOLLOWING => "TK_FOLLOWING", + TK_PARTITION => "TK_PARTITION", + TK_PRECEDING => "TK_PRECEDING", + TK_RANGE => "TK_RANGE", + TK_UNBOUNDED => "TK_UNBOUNDED", + TK_EXCLUDE => "TK_EXCLUDE", + TK_GROUPS => "TK_GROUPS", + TK_OTHERS => "TK_OTHERS", + TK_TIES => "TK_TIES", + TK_GENERATED => "TK_GENERATED", + TK_ALWAYS => "TK_ALWAYS", + TK_MATERIALIZED => "TK_MATERIALIZED", + TK_REINDEX => "TK_REINDEX", + TK_RENAME => "TK_RENAME", + TK_CTIME_KW => "TK_CTIME_KW", + TK_ANY => "TK_ANY", + TK_BITAND => "TK_BITAND", + TK_BITOR => "TK_BITOR", + TK_LSHIFT => "TK_LSHIFT", + TK_RSHIFT => "TK_RSHIFT", + TK_PLUS => "TK_PLUS", + TK_MINUS => "TK_MINUS", + TK_STAR => "TK_STAR", + TK_SLASH => "TK_SLASH", + TK_REM => "TK_REM", + TK_CONCAT => "TK_CONCAT", + TK_PTR => "TK_PTR", + TK_COLLATE => "TK_COLLATE", + TK_BITNOT => "TK_BITNOT", + TK_ON => "TK_ON", + TK_INDEXED => "TK_INDEXED", + TK_STRING => "TK_STRING", + TK_JOIN_KW => "TK_JOIN_KW", + TK_CONSTRAINT => "TK_CONSTRAINT", + TK_DEFAULT => "TK_DEFAULT", + TK_NULL => "TK_NULL", + TK_PRIMARY => "TK_PRIMARY", + TK_UNIQUE => "TK_UNIQUE", + TK_CHECK => "TK_CHECK", + TK_REFERENCES => "TK_REFERENCES", + TK_AUTOINCR => "TK_AUTOINCR", + TK_INSERT => "TK_INSERT", + TK_DELETE => "TK_DELETE", + TK_UPDATE => "TK_UPDATE", + TK_SET => "TK_SET", + TK_DEFERRABLE => "TK_DEFERRABLE", + TK_FOREIGN => "TK_FOREIGN", + TK_DROP => "TK_DROP", + TK_UNION => "TK_UNION", + TK_ALL => "TK_ALL", + TK_EXCEPT => "TK_EXCEPT", + TK_INTERSECT => "TK_INTERSECT", + TK_SELECT => "TK_SELECT", + TK_VALUES => "TK_VALUES", + TK_DISTINCT => "TK_DISTINCT", + TK_DOT => "TK_DOT", + TK_FROM => "TK_FROM", + TK_JOIN => "TK_JOIN", + TK_USING => "TK_USING", + TK_ORDER => "TK_ORDER", + TK_GROUP => "TK_GROUP", + TK_HAVING => "TK_HAVING", + TK_LIMIT => "TK_LIMIT", + TK_WHERE => "TK_WHERE", + TK_RETURNING => "TK_RETURNING", + TK_INTO => "TK_INTO", + TK_NOTHING => "TK_NOTHING", + TK_BLOB => "TK_BLOB", + TK_FLOAT => "TK_FLOAT", + TK_INTEGER => "TK_INTEGER", + TK_VARIABLE => "TK_VARIABLE", + TK_CASE => "TK_CASE", + TK_WHEN => "TK_WHEN", + TK_THEN => "TK_THEN", + TK_ELSE => "TK_ELSE", + TK_INDEX => "TK_INDEX", + TK_ALTER => "TK_ALTER", + TK_ADD => "TK_ADD", + TK_WINDOW => "TK_WINDOW", + TK_OVER => "TK_OVER", + TK_FILTER => "TK_FILTER", + TK_ILLEGAL => "TK_ILLEGAL", + }; + write!(f, "{s}") + } +} + +impl TokenType { + /// if your parsing process expects next token to be TK_ID, remember to call this function !!! + #[inline(always)] + pub fn fallback_id_if_ok(self) -> Self { + use TokenType::*; + match self { + TK_ABORT | TK_ACTION | TK_AFTER | TK_ANALYZE | TK_ASC | TK_ATTACH | TK_BEFORE + | TK_BEGIN | TK_BY | TK_CASCADE | TK_CAST | TK_CONFLICT | TK_DATABASE | TK_DEFERRED + | TK_DESC | TK_DETACH | TK_DO | TK_EACH | TK_END | TK_EXCLUSIVE | TK_EXPLAIN + | TK_FAIL | TK_FOR | TK_IGNORE | TK_IMMEDIATE | TK_INITIALLY | TK_INSTEAD + | TK_LIKE_KW | TK_MATCH | TK_NO | TK_PLAN | TK_QUERY | TK_KEY | TK_OF | TK_OFFSET + | TK_PRAGMA | TK_RAISE | TK_RECURSIVE | TK_RELEASE | TK_REPLACE | TK_RESTRICT + | TK_ROW | TK_ROWS | TK_ROLLBACK | TK_SAVEPOINT | TK_TEMP | TK_TRIGGER | TK_VACUUM + | TK_VIEW | TK_VIRTUAL | TK_WITH | TK_NULLS | TK_FIRST | TK_LAST | TK_CURRENT + | TK_FOLLOWING | TK_PARTITION | TK_PRECEDING | TK_RANGE | TK_UNBOUNDED | TK_EXCLUDE + | TK_GROUPS | TK_OTHERS | TK_TIES | TK_ALWAYS | TK_MATERIALIZED | TK_REINDEX + | TK_RENAME | TK_CTIME_KW | TK_IF => TK_ID, + // | TK_COLUMNKW | TK_UNION | TK_EXCEPT | TK_INTERSECT | TK_GENERATED | TK_WITHOUT + // see comments in `next_token` of parser + _ => self, + } + } +}