Report source location on every parser error
diff --git a/Cargo.lock b/Cargo.lock index ce338b5..42cbb4a 100644 --- a/Cargo.lock +++ b/Cargo.lock
@@ -647,6 +647,7 @@ "serde_json", "simple_logger", "sqlparser_derive", + "thiserror", ] [[package]] @@ -702,6 +703,26 @@ ] [[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] name = "time" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml index 8b1ab55..a32ec2b 100644 --- a/Cargo.toml +++ b/Cargo.toml
@@ -65,6 +65,7 @@ bigdecimal = { version = "0.4.1", features = ["serde"], optional = true } log = "0.4" recursive = { version = "0.1.1", optional = true} +thiserror = { version = "2.0", default-features = false } serde = { version = "1.0", default-features = false, features = ["derive", "alloc"], optional = true } # serde_json is only used in examples/cli, but we have to put it outside
diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index b9427b6..d84e91c 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock
@@ -512,7 +512,7 @@ "prost-build", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -814,6 +814,7 @@ dependencies = [ "log", "recursive", + "thiserror 2.0.21", ] [[package]] @@ -887,7 +888,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", ] [[package]] @@ -902,6 +912,17 @@ ] [[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] name = "typed-path" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/fuzz/fuzz_targets/fuzz_duckdb_accepts.rs b/fuzz/fuzz_targets/fuzz_duckdb_accepts.rs index 0a7e647..9a734ce 100644 --- a/fuzz/fuzz_targets/fuzz_duckdb_accepts.rs +++ b/fuzz/fuzz_targets/fuzz_duckdb_accepts.rs
@@ -197,7 +197,7 @@ return; } match Parser::parse_sql(&DuckDbDialect {}, sql) { - Ok(_) | Err(ParserError::RecursionLimitExceeded) => {} + Ok(_) | Err(ParserError::RecursionLimitExceeded { .. }) => {} Err(err) => { // SAFETY: duckdb_library_version returns a static NUL-terminated string. let version = unsafe { CStr::from_ptr(ffi::duckdb_library_version()) };
diff --git a/fuzz/fuzz_targets/fuzz_postgres_accepts.rs b/fuzz/fuzz_targets/fuzz_postgres_accepts.rs index e387d8a..84fa1bd 100644 --- a/fuzz/fuzz_targets/fuzz_postgres_accepts.rs +++ b/fuzz/fuzz_targets/fuzz_postgres_accepts.rs
@@ -29,7 +29,7 @@ return; }; match Parser::parse_sql(&PostgreSqlDialect {}, sql) { - Ok(_) | Err(ParserError::RecursionLimitExceeded) => {} + Ok(_) | Err(ParserError::RecursionLimitExceeded { .. }) => {} Err(err) => panic!( "PostgreSQL {} accepts SQL the PostgreSQL dialect rejects\n sql: {sql:?}\n error: {err}", parsed.protobuf.version
diff --git a/fuzz/fuzz_targets/fuzz_sqlite_accepts.rs b/fuzz/fuzz_targets/fuzz_sqlite_accepts.rs index 7d45b68..59cde46 100644 --- a/fuzz/fuzz_targets/fuzz_sqlite_accepts.rs +++ b/fuzz/fuzz_targets/fuzz_sqlite_accepts.rs
@@ -137,7 +137,7 @@ return; } match Parser::parse_sql(&SQLiteDialect {}, sql) { - Ok(_) | Err(ParserError::RecursionLimitExceeded) => {} + Ok(_) | Err(ParserError::RecursionLimitExceeded { .. }) => {} Err(err) => { // SAFETY: sqlite3_libversion returns a static NUL terminated string. let version = unsafe { CStr::from_ptr(ffi::sqlite3_libversion()) };
diff --git a/src/ast/helpers/stmt_create_database.rs b/src/ast/helpers/stmt_create_database.rs index e524228..c791402 100644 --- a/src/ast/helpers/stmt_create_database.rs +++ b/src/ast/helpers/stmt_create_database.rs
@@ -28,6 +28,7 @@ CatalogSyncNamespaceMode, ContactEntry, ObjectName, Statement, StorageSerializationPolicy, Tag, }; use crate::parser::ParserError; +use crate::tokenizer::Span; /// Builder for create database statement variant ([1]). /// @@ -357,9 +358,10 @@ with_tags, with_contacts, }), - _ => Err(ParserError::ParserError(format!( - "Expected create database statement, but received: {stmt}" - ))), + _ => Err(ParserError::ParserError { + message: format!("Expected create database statement, but received: {stmt}"), + span: Span::empty(), + }), } } } @@ -369,6 +371,7 @@ use crate::ast::helpers::stmt_create_database::CreateDatabaseBuilder; use crate::ast::{Ident, ObjectName, Statement}; use crate::parser::ParserError; + use crate::tokenizer::Span; #[test] pub fn test_from_valid_statement() { @@ -389,9 +392,10 @@ assert_eq!( CreateDatabaseBuilder::try_from(stmt).unwrap_err(), - ParserError::ParserError( - "Expected create database statement, but received: COMMIT".to_owned() - ) + ParserError::ParserError { + message: "Expected create database statement, but received: COMMIT".to_owned(), + span: Span::empty(), + } ); } }
diff --git a/src/ast/helpers/stmt_create_table.rs b/src/ast/helpers/stmt_create_table.rs index 4ab9ac2..1d5fb6a 100644 --- a/src/ast/helpers/stmt_create_table.rs +++ b/src/ast/helpers/stmt_create_table.rs
@@ -33,6 +33,7 @@ }; use crate::parser::ParserError; +use crate::tokenizer::Span; /// Builder for create table statement variant ([1]). /// @@ -667,9 +668,10 @@ fn try_from(stmt: Statement) -> Result<Self, Self::Error> { match stmt { Statement::CreateTable(create_table) => Ok(create_table.into()), - _ => Err(ParserError::ParserError(format!( - "Expected create table statement, but received: {stmt}" - ))), + _ => Err(ParserError::ParserError { + message: format!("Expected create table statement, but received: {stmt}"), + span: Span::empty(), + }), } } } @@ -758,6 +760,7 @@ use crate::ast::helpers::stmt_create_table::CreateTableBuilder; use crate::ast::{Ident, ObjectName, Statement}; use crate::parser::ParserError; + use crate::tokenizer::Span; #[test] pub fn test_from_valid_statement() { @@ -779,9 +782,10 @@ assert_eq!( CreateTableBuilder::try_from(stmt).unwrap_err(), - ParserError::ParserError( - "Expected create table statement, but received: COMMIT".to_owned() - ) + ParserError::ParserError { + message: "Expected create table statement, but received: COMMIT".to_owned(), + span: Span::empty(), + } ); } }
diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index ce4cde4..2a7cfc1 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs
@@ -38,7 +38,7 @@ }; use crate::dialect::{Dialect, Precedence}; use crate::keywords::Keyword; -use crate::parser::{IsOptional, Parser, ParserError}; +use crate::parser::{parser_err, IsOptional, Parser, ParserError}; use crate::tokenizer::TokenWithSpan; use crate::tokenizer::{Span, Token}; #[cfg(not(feature = "std"))] @@ -461,7 +461,7 @@ } else if parser.parse_keywords(&[Keyword::TAG]) { Ok(parse_column_tags(parser, with).map(|p| Some(ColumnOption::Tags(p)))) } else { - Err(ParserError::ParserError("not found match".to_string())) + parser_err!("not found match", parser.peek_token_ref().span) } }) } @@ -768,9 +768,10 @@ Keyword::REMOVE | Keyword::RM => { Ok(Statement::Remove(FileStagingCommand { stage, pattern })) } - _ => Err(ParserError::ParserError( - "unexpected stage command, expecting LIST, LS, REMOVE or RM".to_string(), - )), + _ => parser_err!( + "unexpected stage command, expecting LIST, LS, REMOVE or RM", + parser.get_current_token().span + ), } } @@ -1121,9 +1122,10 @@ builder = builder.table_options(table_options); if iceberg && builder.base_location.is_none() { - return Err(ParserError::ParserError( - "BASE_LOCATION is required for ICEBERG tables".to_string(), - )); + return parser_err!( + "BASE_LOCATION is required for ICEBERG tables", + parser.peek_token_ref().span + ); } Ok(builder.build()) @@ -1592,9 +1594,15 @@ let next_token = parser.next_token(); match next_token.token { Token::Placeholder(w) => { - file_col_num = w.to_string().split_off(1).parse::<i32>().map_err(|e| { - ParserError::ParserError(format!("Could not parse '{w}' as i32: {e}")) - })?; + file_col_num = match w.to_string().split_off(1).parse::<i32>() { + Ok(n) => n, + Err(e) => { + return parser_err!( + format!("Could not parse '{w}' as i32: {e}"), + next_token.span + ) + } + }; Ok(()) } Token::Word(w) => { @@ -1610,9 +1618,15 @@ let col_num_token = parser.next_token(); match col_num_token.token { Token::Placeholder(w) => { - file_col_num = w.to_string().split_off(1).parse::<i32>().map_err(|e| { - ParserError::ParserError(format!("Could not parse '{w}' as i32: {e}")) - })?; + file_col_num = match w.to_string().split_off(1).parse::<i32>() { + Ok(n) => n, + Err(e) => { + return parser_err!( + format!("Could not parse '{w}' as i32: {e}"), + col_num_token.span + ) + } + }; Ok(()) } _ => parser.expected("file_col_num", col_num_token), @@ -1763,9 +1777,7 @@ } } if options.is_empty() { - Err(ParserError::ParserError( - "expected at least one option".to_string(), - )) + parser_err!("expected at least one option", parser.peek_token_ref().span) } else { Ok(options) }
diff --git a/src/parser/alter.rs b/src/parser/alter.rs index 4000eb2..d0e25e9 100644 --- a/src/parser/alter.rs +++ b/src/parser/alter.rs
@@ -38,9 +38,10 @@ return self.parse_mssql_alter_role(); } - Err(ParserError::ParserError( - "ALTER ROLE is only support for PostgreSqlDialect, MsSqlDialect".into(), - )) + parser_err!( + "ALTER ROLE is only support for PostgreSqlDialect, MsSqlDialect", + self.get_current_token().span + ) } /// Parse ALTER POLICY statement
diff --git a/src/parser/merge.rs b/src/parser/merge.rs index 79a77df..53ddd87 100644 --- a/src/parser/merge.rs +++ b/src/parser/merge.rs
@@ -123,8 +123,8 @@ MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget ) { return parser_err!( - format_args!("UPDATE is not allowed in a {clause_kind} merge clause"), - self.get_current_token().span.start + format!("UPDATE is not allowed in a {clause_kind} merge clause"), + self.get_current_token().span ); } @@ -159,8 +159,8 @@ MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget ) { return parser_err!( - format_args!("DELETE is not allowed in a {clause_kind} merge clause"), - self.get_current_token().span.start + format!("DELETE is not allowed in a {clause_kind} merge clause"), + self.get_current_token().span ); }; @@ -175,8 +175,8 @@ MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget ) { return parser_err!( - format_args!("INSERT is not allowed in a {clause_kind} merge clause"), - self.get_current_token().span.start + format!("INSERT is not allowed in a {clause_kind} merge clause"), + self.get_current_token().span ); }; @@ -222,7 +222,7 @@ _ => { return parser_err!( "expected UPDATE, DELETE, INSERT or DO NOTHING in merge clause", - self.peek_token_ref().span.start + self.peek_token_ref().span ); } };
diff --git a/src/parser/mod.rs b/src/parser/mod.rs index db54296..3085a83 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs
@@ -21,10 +21,7 @@ vec, vec::Vec, }; -use core::{ - fmt::{self, Display}, - str::FromStr, -}; +use core::{fmt::Display, str::FromStr}; #[cfg(feature = "std")] use std::collections::{BTreeMap, BTreeSet}; @@ -32,7 +29,7 @@ use log::debug; -use recursion::RecursionCounter; +use recursion::{DepthGuard, RecursionCounter}; use IsLateral::*; use IsOptional::*; @@ -52,22 +49,53 @@ use sqlparser::parser::ParserState::ColumnDefinition; /// Errors produced by the SQL parser. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum ParserError { - /// Error originating from the tokenizer with a message. - TokenizerError(String), - /// Generic parser error with a message. - ParserError(String), + /// Error originating from the tokenizer. + #[error("sql parser error: {message}{}", .span.start)] + TokenizerError { + /// Description of the error. + message: String, + /// Where the error was detected, empty when unknown. + span: Span, + }, + /// Generic parser error. + #[error("sql parser error: {message}{}", .span.start)] + ParserError { + /// Description of the error. + message: String, + /// The offending source, empty when unknown. + span: Span, + }, /// Raised when a recursion depth limit is exceeded. - RecursionLimitExceeded, + #[error("sql parser error: recursion limit exceeded{}", .span.start)] + RecursionLimitExceeded { + /// The token at which the limit was reached, empty when unknown. + span: Span, + }, +} + +impl ParserError { + /// The source span the error refers to, empty when unknown. + pub fn span(&self) -> Span { + match self { + ParserError::TokenizerError { span, .. } + | ParserError::ParserError { span, .. } + | ParserError::RecursionLimitExceeded { span } => *span, + } + } } // Use `Parser::expected` instead, if possible macro_rules! parser_err { - ($MSG:expr, $loc:expr) => { - Err(ParserError::ParserError(format!("{}{}", $MSG, $loc))) + ($MSG:expr, $span:expr) => { + Err(ParserError::ParserError { + message: ::alloc::string::String::from($MSG), + span: $span, + }) }; } +pub(crate) use parser_err; mod alter; mod merge; @@ -80,8 +108,6 @@ use alloc::rc::Rc; use core::cell::Cell; - use super::ParserError; - /// Tracks remaining recursion depth. This value is decremented on /// each call to [`RecursionCounter::try_decrease()`], when it reaches 0 an error will /// be returned. @@ -107,18 +133,18 @@ /// Decreases the remaining depth by 1. /// - /// Returns [`Err`] if the remaining depth falls to 0. + /// Returns [`None`] if the remaining depth is already 0. /// /// Returns a [`DepthGuard`] which will adds 1 to the /// remaining depth upon drop; - pub fn try_decrease(&self) -> Result<DepthGuard, ParserError> { + pub fn try_decrease(&self) -> Option<DepthGuard> { let old_value = self.remaining_depth.get(); // ran out of space if old_value == 0 { - Err(ParserError::RecursionLimitExceeded) + None } else { self.remaining_depth.set(old_value - 1); - Ok(DepthGuard::new(Rc::clone(&self.remaining_depth))) + Some(DepthGuard::new(Rc::clone(&self.remaining_depth))) } } } @@ -171,38 +197,16 @@ impl From<TokenizerError> for ParserError { fn from(e: TokenizerError) -> Self { - ParserError::TokenizerError(e.to_string()) + ParserError::TokenizerError { + message: e.message, + span: Span::new(e.location, e.location), + } } } -impl fmt::Display for ParserError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "sql parser error: {}", - match self { - ParserError::TokenizerError(s) => s, - ParserError::ParserError(s) => s, - ParserError::RecursionLimitExceeded => "recursion limit exceeded", - } - ) - } -} - -impl core::error::Error for ParserError {} - // By default, allow expressions up to this deep before erroring const DEFAULT_REMAINING_DEPTH: usize = 50; -// A constant EOF token that can be referenced. -const EOF_TOKEN: TokenWithSpan = TokenWithSpan { - token: Token::EOF, - span: Span { - start: Location { line: 0, column: 0 }, - end: Location { line: 0, column: 0 }, - }, -}; - /// Composite types declarations using angle brackets syntax can be arbitrary /// nested such that the following declaration is possible: /// `ARRAY<ARRAY<INT>>` @@ -313,7 +317,7 @@ /// * The "previous" token is the token at `index - 2` /// /// If `index` is equal to the length of the token stream, the 'next' token is -/// [`Token::EOF`]. +/// [`Token::EOF`], located right after the last non-whitespace token. /// /// For example, the SQL string "SELECT * FROM foo" will be tokenized into /// following tokens: @@ -333,6 +337,8 @@ pub struct Parser<'a> { /// The tokens tokens: Vec<TokenWithSpan>, + /// Returned when reading past the end of [`Parser::tokens`]. + eof_token: TokenWithSpan, /// The index of the first unprocessed token in [`Parser::tokens`]. index: usize, /// The current state of the parser. @@ -370,14 +376,14 @@ /// memoization, so the caches hold no strings. #[derive(Debug, Clone, Copy)] enum ExprPrefixError { - RecursionLimitExceeded, + RecursionLimitExceeded(Span), Err, } impl From<&ParserError> for ExprPrefixError { fn from(e: &ParserError) -> Self { match e { - ParserError::RecursionLimitExceeded => Self::RecursionLimitExceeded, + ParserError::RecursionLimitExceeded { span } => Self::RecursionLimitExceeded(*span), _ => Self::Err, } } @@ -402,6 +408,7 @@ pub fn new(dialect: &'a dyn Dialect) -> Self { Self { tokens: vec![], + eof_token: TokenWithSpan::new_eof(), index: 0, state: ParserState::Normal, dialect, @@ -430,7 +437,7 @@ /// .with_recursion_limit(1) /// .try_with_sql("SELECT * FROM foo WHERE (a OR (b OR (c OR d)))")? /// .parse_statements(); - /// assert_eq!(result, Err(ParserError::RecursionLimitExceeded)); + /// assert!(matches!(result, Err(ParserError::RecursionLimitExceeded { .. }))); /// # Ok(()) /// # } /// ``` @@ -446,6 +453,16 @@ self } + /// Enters one level of recursion, failing at the next token once the + /// limit set by [`Parser::with_recursion_limit`] is reached. + fn recursion_guard(&self) -> Result<DepthGuard, ParserError> { + self.recursion_counter + .try_decrease() + .ok_or_else(|| ParserError::RecursionLimitExceeded { + span: self.peek_token_ref().span, + }) + } + /// Specify additional parser options /// /// [`Parser`] supports additional options ([`ParserOptions`]) @@ -475,6 +492,13 @@ /// Reset this parser to parse the specified token stream pub fn with_tokens_with_locations(mut self, tokens: Vec<TokenWithSpan>) -> Self { + let end = tokens + .iter() + .rfind(|t| !matches!(t.token, Token::Whitespace(_))) + .map(|t| t.span.end) + .or_else(|| tokens.first().map(|t| t.span.start)) + .unwrap_or_else(Location::empty); + self.eof_token = TokenWithSpan::new(Token::EOF, Span::new(end, end)); self.tokens = tokens; self.index = 0; self.failed_prefix_positions.clear(); @@ -509,7 +533,12 @@ let tokens = Tokenizer::new(self.dialect, sql) .with_unescape(self.options.unescape) .tokenize_with_location()?; - Ok(self.with_tokens_with_locations(tokens)) + let mut parser = self.with_tokens_with_locations(tokens); + if parser.tokens.is_empty() { + let start = Location::new(1, 1); + parser.eof_token.span = Span::new(start, start); + } + Ok(parser) } /// Parse potentially multiple statements @@ -622,7 +651,7 @@ /// Parse a single top-level statement (such as SELECT, INSERT, CREATE, etc.), /// stopping before the statement separator, if any. pub fn parse_statement(&mut self) -> Result<Statement, ParserError> { - let _guard = self.recursion_counter.try_decrease()?; + let _guard = self.recursion_guard()?; // allow the dialect to override statement parsing if let Some(statement) = self.dialect.parse_statement(self) { @@ -1015,10 +1044,7 @@ let mut export = false; if !dialect_of!(self is MySqlDialect | GenericDialect) { - return parser_err!( - "Unsupported statement FLUSH", - self.peek_token_ref().span.start - ); + return parser_err!("Unsupported statement FLUSH", self.peek_token_ref().span); } let location = if self.parse_keyword(Keyword::NO_WRITE_TO_BINLOG) { @@ -1348,9 +1374,10 @@ Token::Word(w) => w.into_ident(next_token.span), Token::SingleQuotedString(s) => Ident::with_quote('\'', s), _ => { - return Err(ParserError::ParserError( - "Internal parser error: unexpected token type".to_string(), - )) + return parser_err!( + "Internal parser error: unexpected token type", + next_token.span + ) } }]; @@ -1428,7 +1455,7 @@ /// Parse tokens until the precedence changes. #[cfg_attr(feature = "recursive-protection", recursive::recursive)] pub fn parse_subexpr(&mut self, precedence: u8) -> Result<Expr, ParserError> { - let _guard = self.recursion_counter.try_decrease()?; + let _guard = self.recursion_guard()?; debug!("parsing expr"); let mut expr = self.parse_prefix()?; @@ -1789,7 +1816,9 @@ found: &TokenWithSpan, ) -> Result<T, ParserError> { match cached { - ExprPrefixError::RecursionLimitExceeded => Err(ParserError::RecursionLimitExceeded), + ExprPrefixError::RecursionLimitExceeded(span) => { + Err(ParserError::RecursionLimitExceeded { span }) + } ExprPrefixError::Err => self.expected_ref("an expression", found), } } @@ -1811,7 +1840,7 @@ // Note also that naively `SELECT date` looks like a syntax error because the `date` type // name is not followed by a string literal, but in fact in PostgreSQL it is a valid // expression that should parse as the column name "date". - let loc = self.peek_token_ref().span.start; + let loc = self.peek_token_ref().span; let opt_expr = self.maybe_parse(|parser| { match parser.parse_data_type()? { DataType::Interval { .. } => parser.parse_interval(), @@ -1950,9 +1979,10 @@ Token::PGCubeRoot => UnaryOperator::PGCubeRoot, Token::AtSign => UnaryOperator::PGAbs, _ => { - return Err(ParserError::ParserError( - "Internal parser error: unexpected unary operator token".to_string(), - )) + return parser_err!( + "Internal parser error: unexpected unary operator token", + self.get_current_token().span + ) } }; Ok(Expr::UnaryOp { @@ -1980,9 +2010,10 @@ Token::QuestionMarkDash => UnaryOperator::QuestionDash, Token::QuestionPipe => UnaryOperator::QuestionPipe, _ => { - return Err(ParserError::ParserError(format!( - "Unexpected token in unary operator parsing: {tok:?}" - ))) + return parser_err!( + format!("Unexpected token in unary operator parsing: {tok:?}"), + self.get_current_token().span + ) } }; Ok(Expr::UnaryOp { @@ -2023,34 +2054,35 @@ Ok(Expr::Value(self.parse_value()?)) } Token::LParen => { - let expr = - if let Some(expr) = self.try_parse_expr_sub_query()? { - expr - } else if let Some(lambda) = self.try_parse_lambda()? { - return Ok(lambda); - } else { - // Parentheses in expressions switch to "normal" parsing state. - // This matters for dialects (SQLite, DuckDB) where `NOT NULL` can - // be an alias for `IS NOT NULL`. In column definitions like: - // - // CREATE TABLE t (c INT DEFAULT (42 NOT NULL) NOT NULL) - // - // The `(42 NOT NULL)` is an expression with parens, so it parses - // as `IsNotNull(42)`. The trailing `NOT NULL` is outside those - // expression parens (the outer parens are CREATE TABLE syntax), - // so it remains a column constraint. - let exprs = self.with_state(ParserState::Normal, |p| { - p.parse_comma_separated(Parser::parse_expr) - })?; - match exprs.len() { - 0 => return Err(ParserError::ParserError( - "Internal parser error: parse_comma_separated returned empty list" - .to_string(), - )), - 1 => Expr::Nested(Box::new(exprs.into_iter().next().unwrap())), - _ => Expr::Tuple(exprs), + let expr = if let Some(expr) = self.try_parse_expr_sub_query()? { + expr + } else if let Some(lambda) = self.try_parse_lambda()? { + return Ok(lambda); + } else { + // Parentheses in expressions switch to "normal" parsing state. + // This matters for dialects (SQLite, DuckDB) where `NOT NULL` can + // be an alias for `IS NOT NULL`. In column definitions like: + // + // CREATE TABLE t (c INT DEFAULT (42 NOT NULL) NOT NULL) + // + // The `(42 NOT NULL)` is an expression with parens, so it parses + // as `IsNotNull(42)`. The trailing `NOT NULL` is outside those + // expression parens (the outer parens are CREATE TABLE syntax), + // so it remains a column constraint. + let exprs = self.with_state(ParserState::Normal, |p| { + p.parse_comma_separated(Parser::parse_expr) + })?; + match exprs.len() { + 0 => { + return parser_err!( + "Internal parser error: parse_comma_separated returned empty list", + self.peek_token_ref().span + ) } - }; + 1 => Expr::Nested(Box::new(exprs.into_iter().next().unwrap())), + _ => Expr::Tuple(exprs), + } + }; self.expect_token(&Token::RParen)?; Ok(expr) } @@ -2258,7 +2290,7 @@ .all(|access| matches!(access, AccessExpr::Dot(Expr::Identifier(_)))) { let Some(AccessExpr::Dot(Expr::Function(mut func))) = access_chain.pop() else { - return parser_err!("expected function expression", root.span().start); + return parser_err!("expected function expression", root.span()); }; let compound_func_name = [root] @@ -2290,14 +2322,14 @@ ) { let Some(AccessExpr::Dot(Expr::OuterJoin(inner_expr))) = access_chain.pop() else { - return parser_err!("expected (+) expression", root.span().start); + return parser_err!("expected (+) expression", root.span()); }; if !Self::is_all_ident(&root, &[]) { - return parser_err!("column identifier before (+)", root.span().start); + return parser_err!("column identifier before (+)", root.span()); }; - let token_start = root.span().start; + let token_start = root.span(); let mut idents = Self::exprs_to_idents(root, vec![])?; match *inner_expr { Expr::CompoundIdentifier(suffix) => idents.extend(suffix), @@ -2344,18 +2376,12 @@ if let AccessExpr::Dot(Expr::Identifier(ident)) = x { idents.push(ident); } else { - return parser_err!( - format!("Expected identifier, found: {}", x), - x.span().start - ); + return parser_err!(format!("Expected identifier, found: {}", x), x.span()); } } Ok(idents) } else { - parser_err!( - format!("Expected identifier, found: {}", root), - root.span().start - ) + parser_err!(format!("Expected identifier, found: {}", root), root.span()) } } @@ -2977,9 +3003,7 @@ { ExtractSyntax::Comma } else { - return Err(ParserError::ParserError( - "Expected 'FROM' or ','".to_string(), - )); + return self.expected_ref("'FROM' or ','", self.peek_token_ref()); }; let expr = self.parse_expr()?; @@ -3005,9 +3029,7 @@ if matches!(v.value, Value::Number(_, _)) { CeilFloorKind::Scale(v) } else { - return Err(ParserError::ParserError( - "Scale field can only be of number type".to_string(), - )); + return parser_err!("Scale field can only be of number type", v.span); } } else { CeilFloorKind::DateTimeField(DateTimeField::NoDateTime) @@ -3436,7 +3458,7 @@ /// Note that we do not currently attempt to parse the quoted value. #[cfg_attr(feature = "recursive-protection", recursive::recursive)] pub fn parse_interval(&mut self) -> Result<Expr, ParserError> { - let _guard = self.recursion_counter.try_decrease()?; + let _guard = self.recursion_guard()?; // The SQL standard allows an optional sign before the value string, but // it is not clear if any implementations support that syntax, so we @@ -3465,7 +3487,7 @@ } else if self.dialect.require_interval_qualifier() { return parser_err!( "INTERVAL requires a unit after the literal value", - self.peek_token_ref().span.start + self.peek_token_ref().span ); } else { None @@ -3564,10 +3586,7 @@ let (fields, trailing_bracket) = self.parse_struct_type_def(Self::parse_struct_field_def)?; if trailing_bracket.0 { - return parser_err!( - "unmatched > in STRUCT literal", - self.peek_token_ref().span.start - ); + return parser_err!("unmatched > in STRUCT literal", self.peek_token_ref().span); } // Parse the struct values `(expr1 [, ... ])` @@ -3598,7 +3617,7 @@ if typed_syntax { return parser_err!("Typed syntax does not allow AS", { self.prev_token(); - self.peek_token_ref().span.start + self.peek_token_ref().span }); } let field_name = self.parse_identifier()?; @@ -4094,7 +4113,7 @@ format!( "Expected one of [=, >, <, =>, =<, !=, ~, ~*, !~, !~*, ~~, ~~*, !~~, !~~*] as comparison operator, found: {op}" ), - span.start + span ); }; @@ -4110,9 +4129,7 @@ right: Box::new(right), is_some: keyword == Keyword::SOME, }, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: expected any of {{ALL, ANY, SOME}}, got {unexpected_keyword:?}"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: expected any of {{ALL, ANY, SOME}}, got {unexpected_keyword:?}"), self.get_current_token().span), }) } else { Ok(Expr::BinaryOp { @@ -4266,7 +4283,7 @@ // Can only happen if `get_next_precedence` got out of sync with this function _ => parser_err!( format!("No infix parser for token {:?}", tok.token), - tok.span.start + tok.span ), } } else if Token::DoubleColon == *tok { @@ -4290,7 +4307,7 @@ // Can only happen if `get_next_precedence` got out of sync with this function parser_err!( format!("No infix parser for token {:?}", tok.token), - tok.span.start + tok.span ) } } @@ -4535,7 +4552,7 @@ negated, global, }), - Err(ParserError::RecursionLimitExceeded) => Err(ParserError::RecursionLimitExceeded), + Err(e @ ParserError::RecursionLimitExceeded { .. }) => Err(e), Err(e) => match missing_rparen { Some(index) if self.index <= index => { self.index = index; @@ -4579,7 +4596,7 @@ /// Return the token at the given location, or EOF if the index is beyond /// the length of the current set of tokens. pub fn token_at(&self, index: usize) -> &TokenWithSpan { - self.tokens.get(index).unwrap_or(&EOF_TOKEN) + self.tokens.get(index).unwrap_or(&self.eof_token) } /// Return the first non-whitespace token that has not yet been processed @@ -4639,10 +4656,7 @@ { continue; } - break token.cloned().unwrap_or(TokenWithSpan { - token: Token::EOF, - span: Span::empty(), - }); + break token.cloned().unwrap_or_else(|| self.eof_token.clone()); }) } @@ -4662,7 +4676,7 @@ { continue; } - break token.unwrap_or(&EOF_TOKEN); + break token.unwrap_or(&self.eof_token); }) } @@ -4683,7 +4697,7 @@ }) => continue, non_whitespace => { if n == 0 { - return non_whitespace.unwrap_or(&EOF_TOKEN); + return non_whitespace.unwrap_or(&self.eof_token); } n -= 1; } @@ -4710,7 +4724,7 @@ /// Return nth token, possibly whitespace, that has not yet been processed. fn peek_nth_token_no_skip_ref(&self, n: usize) -> &TokenWithSpan { - self.tokens.get(self.index + n).unwrap_or(&EOF_TOKEN) + self.tokens.get(self.index + n).unwrap_or(&self.eof_token) } /// Return true if the next tokens exactly `expected` @@ -4806,27 +4820,18 @@ /// Report `found` was encountered instead of `expected` pub fn expected<T>(&self, expected: &str, found: TokenWithSpan) -> Result<T, ParserError> { - parser_err!( - format!("Expected: {expected}, found: {found}"), - found.span.start - ) + parser_err!(format!("Expected: {expected}, found: {found}"), found.span) } /// report `found` was encountered instead of `expected` pub fn expected_ref<T>(&self, expected: &str, found: &TokenWithSpan) -> Result<T, ParserError> { - parser_err!( - format!("Expected: {expected}, found: {found}"), - found.span.start - ) + parser_err!(format!("Expected: {expected}, found: {found}"), found.span) } /// Report that the token at `index` was found instead of `expected`. pub fn expected_at<T>(&self, expected: &str, index: usize) -> Result<T, ParserError> { - let found = self.tokens.get(index).unwrap_or(&EOF_TOKEN); - parser_err!( - format!("Expected: {expected}, found: {found}"), - found.span.start - ) + let found = self.tokens.get(index).unwrap_or(&self.eof_token); + parser_err!(format!("Expected: {expected}, found: {found}"), found.span) } /// If the current token is the `expected` keyword, consume it and returns @@ -5031,15 +5036,16 @@ } } - fn parse<T: FromStr>(s: String, loc: Location) -> Result<T, ParserError> + fn parse<T: FromStr>(s: String, span: Span) -> Result<T, ParserError> where <T as FromStr>::Err: Display, { - s.parse::<T>().map_err(|e| { - ParserError::ParserError(format!( - "Could not parse '{s}' as {}: {e}{loc}", + s.parse::<T>().map_err(|e| ParserError::ParserError { + message: format!( + "Could not parse '{s}' as {}: {e}", core::any::type_name::<T>() - )) + ), + span, }) } @@ -5284,7 +5290,7 @@ { match self.try_parse(f) { Ok(t) => Ok(Some(t)), - Err(ParserError::RecursionLimitExceeded) => Err(ParserError::RecursionLimitExceeded), + Err(e @ ParserError::RecursionLimitExceeded { .. }) => Err(e), _ => Ok(None), } } @@ -5308,7 +5314,7 @@ /// Parse either `ALL`, `DISTINCT` or `DISTINCT ON (...)`. Returns [`None`] if `ALL` is parsed /// and results in a [`ParserError`] if both `ALL` and `DISTINCT` are found. pub fn parse_all_or_distinct(&mut self) -> Result<Option<Distinct>, ParserError> { - let loc = self.peek_token_ref().span.start; + let loc = self.peek_token_ref().span; let distinct = match self.parse_one_of_keywords(&[Keyword::ALL, Keyword::DISTINCT]) { Some(Keyword::ALL) => { if self.peek_keyword(Keyword::DISTINCT) { @@ -5346,7 +5352,7 @@ /// Parse a SQL CREATE statement pub fn parse_create(&mut self) -> Result<Statement, ParserError> { - let modifier_loc = self.peek_token_ref().span.start; + let modifier_span = self.peek_token_ref().span; let or_replace = self.parse_keywords(&[Keyword::OR, Keyword::REPLACE]); let or_alter = self.parse_keywords(&[Keyword::OR, Keyword::ALTER]); let multiset = self.maybe_parse_multiset(); @@ -5466,7 +5472,7 @@ { return parser_err!( "CREATE FOREIGN TABLE does not accept this modifier", - modifier_loc + modifier_span ); } self.parse_create_foreign_table().map(Into::into) @@ -5486,9 +5492,9 @@ Keyword::CONFIGURATION => Ok(TextSearchObjectType::Configuration), Keyword::TEMPLATE => Ok(TextSearchObjectType::Template), Keyword::PARSER => Ok(TextSearchObjectType::Parser), - unexpected_keyword => Err(ParserError::ParserError(format!( + unexpected_keyword => parser_err!(format!( "Internal parser error: expected any of {{DICTIONARY, CONFIGURATION, TEMPLATE, PARSER}}, got {unexpected_keyword:?}" - ))), + ), self.get_current_token().span), } } @@ -5992,28 +5998,31 @@ let mut body = Body::default(); let mut set_params: Vec<FunctionDefinitionSetParam> = Vec::new(); loop { - fn ensure_not_set<T>(field: &Option<T>, name: &str) -> Result<(), ParserError> { + fn ensure_not_set<T>( + field: &Option<T>, + name: &str, + span: Span, + ) -> Result<(), ParserError> { if field.is_some() { - return Err(ParserError::ParserError(format!( - "{name} specified more than once", - ))); + return parser_err!(format!("{name} specified more than once"), span); } Ok(()) } + let clause = self.peek_token_ref().span; if self.parse_keyword(Keyword::AS) { - ensure_not_set(&body.function_body, "AS")?; + ensure_not_set(&body.function_body, "AS", clause)?; body.function_body = Some(self.parse_create_function_body_string()?); } else if self.parse_keyword(Keyword::LANGUAGE) { - ensure_not_set(&body.language, "LANGUAGE")?; + ensure_not_set(&body.language, "LANGUAGE", clause)?; body.language = Some(self.parse_identifier()?); } else if self.parse_keyword(Keyword::IMMUTABLE) { - ensure_not_set(&body.behavior, "IMMUTABLE | STABLE | VOLATILE")?; + ensure_not_set(&body.behavior, "IMMUTABLE | STABLE | VOLATILE", clause)?; body.behavior = Some(FunctionBehavior::Immutable); } else if self.parse_keyword(Keyword::STABLE) { - ensure_not_set(&body.behavior, "IMMUTABLE | STABLE | VOLATILE")?; + ensure_not_set(&body.behavior, "IMMUTABLE | STABLE | VOLATILE", clause)?; body.behavior = Some(FunctionBehavior::Stable); } else if self.parse_keyword(Keyword::VOLATILE) { - ensure_not_set(&body.behavior, "IMMUTABLE | STABLE | VOLATILE")?; + ensure_not_set(&body.behavior, "IMMUTABLE | STABLE | VOLATILE", clause)?; body.behavior = Some(FunctionBehavior::Volatile); } else if self.parse_keywords(&[ Keyword::CALLED, @@ -6024,6 +6033,7 @@ ensure_not_set( &body.called_on_null, "CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT", + clause, )?; body.called_on_null = Some(FunctionCalledOnNull::CalledOnNullInput); } else if self.parse_keywords(&[ @@ -6036,16 +6046,22 @@ ensure_not_set( &body.called_on_null, "CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT", + clause, )?; body.called_on_null = Some(FunctionCalledOnNull::ReturnsNullOnNullInput); } else if self.parse_keyword(Keyword::STRICT) { ensure_not_set( &body.called_on_null, "CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT", + clause, )?; body.called_on_null = Some(FunctionCalledOnNull::Strict); } else if self.parse_keyword(Keyword::PARALLEL) { - ensure_not_set(&body.parallel, "PARALLEL { UNSAFE | RESTRICTED | SAFE }")?; + ensure_not_set( + &body.parallel, + "PARALLEL { UNSAFE | RESTRICTED | SAFE }", + clause, + )?; if self.parse_keyword(Keyword::UNSAFE) { body.parallel = Some(FunctionParallel::Unsafe); } else if self.parse_keyword(Keyword::RESTRICTED) { @@ -6057,7 +6073,7 @@ .expected_ref("one of UNSAFE | RESTRICTED | SAFE", self.peek_token_ref()); } } else if self.parse_keyword(Keyword::SECURITY) { - ensure_not_set(&body.security, "SECURITY { DEFINER | INVOKER }")?; + ensure_not_set(&body.security, "SECURITY { DEFINER | INVOKER }", clause)?; if self.parse_keyword(Keyword::DEFINER) { body.security = Some(FunctionSecurity::Definer); } else if self.parse_keyword(Keyword::INVOKER) { @@ -6082,7 +6098,7 @@ }; set_params.push(FunctionDefinitionSetParam { name, value }); } else if self.parse_keyword(Keyword::RETURN) { - ensure_not_set(&body.function_body, "RETURN")?; + ensure_not_set(&body.function_body, "RETURN", clause)?; body.function_body = Some(CreateFunctionBody::Return(self.parse_expr()?)); } else { break; @@ -6252,7 +6268,7 @@ } _ => parser_err!( "Expected table column definitions after TABLE keyword", - p.peek_token_ref().span.start + p.peek_token_ref().span )?, }; @@ -6289,11 +6305,11 @@ } else { parser_err!( "Expected a subquery (or bare SELECT statement) after RETURN", - self.peek_token_ref().span.start + self.peek_token_ref().span )? } } else { - parser_err!("Unparsable function body", self.peek_token_ref().span.start)? + parser_err!("Unparsable function body", self.peek_token_ref().span)? }; Ok(CreateFunction { @@ -6381,7 +6397,7 @@ // This dummy error is ignored in `maybe_parse` parser_err!( "The DEFAULT keyword is not a type", - parser.peek_token_ref().span.start + parser.peek_token_ref().span ) } else { parser.parse_data_type() @@ -6445,7 +6461,7 @@ // Dummy error ignored by maybe_parse parser_err!( "The current token cannot start an aggregate argument type", - parser.peek_token_ref().span.start + parser.peek_token_ref().span ) } else { parser.parse_data_type() @@ -6498,9 +6514,7 @@ let option = match self.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) { Some(Keyword::CASCADE) => Some(ReferentialAction::Cascade), Some(Keyword::RESTRICT) => Some(ReferentialAction::Restrict), - Some(unexpected_keyword) => return Err(ParserError::ParserError( - format!("Internal parser error: expected any of {{CASCADE, RESTRICT}}, got {unexpected_keyword:?}"), - )), + Some(unexpected_keyword) => return parser_err!(format!("Internal parser error: expected any of {{CASCADE, RESTRICT}}, got {unexpected_keyword:?}"), self.get_current_token().span), None => None, }; Ok(DropTrigger { @@ -6553,9 +6567,7 @@ match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? { Keyword::ROW => TriggerObject::Row, Keyword::STATEMENT => TriggerObject::Statement, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in ROW/STATEMENT"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in ROW/STATEMENT"), self.get_current_token().span), }; Some(if include_each { @@ -6618,9 +6630,7 @@ Keyword::INSTEAD => self .expect_keyword_is(Keyword::OF) .map(|_| TriggerPeriod::InsteadOf)?, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in trigger period"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in trigger period"), self.get_current_token().span), }, ) } @@ -6645,9 +6655,7 @@ } Keyword::DELETE => TriggerEvent::Delete, Keyword::TRUNCATE => TriggerEvent::Truncate, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in trigger event"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in trigger event"), self.get_current_token().span), }, ) } @@ -6683,9 +6691,7 @@ { Keyword::FUNCTION => TriggerExecBodyType::Function, Keyword::PROCEDURE => TriggerExecBodyType::Procedure, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in trigger exec body"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in trigger exec body"), self.get_current_token().span), }, func_desc: self.parse_function_desc()?, }) @@ -7085,10 +7091,7 @@ let mut admin = vec![]; while let Some(keyword) = self.parse_one_of_keywords(&optional_keywords) { - let loc = self - .tokens - .get(self.index - 1) - .map_or(Location { line: 0, column: 0 }, |t| t.span.start); + let loc = self.get_current_token().span; match keyword { Keyword::AUTHORIZATION => { if authorization_owner.is_some() { @@ -7255,21 +7258,31 @@ /// Parse an `OWNER` clause. pub fn parse_owner(&mut self) -> Result<Owner, ParserError> { - let owner = match self.parse_one_of_keywords(&[Keyword::CURRENT_USER, Keyword::CURRENT_ROLE, Keyword::SESSION_USER]) { + let owner = match self.parse_one_of_keywords(&[ + Keyword::CURRENT_USER, + Keyword::CURRENT_ROLE, + Keyword::SESSION_USER, + ]) { Some(Keyword::CURRENT_USER) => Owner::CurrentUser, Some(Keyword::CURRENT_ROLE) => Owner::CurrentRole, Some(Keyword::SESSION_USER) => Owner::SessionUser, - Some(unexpected_keyword) => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in owner"), - )), - None => { - match self.parse_identifier() { - Ok(ident) => Owner::Ident(ident), - Err(e) => { - return Err(ParserError::ParserError(format!("Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. {e}"))) - } - } + Some(unexpected_keyword) => { + return parser_err!( + format!( + "Internal parser error: unexpected keyword `{unexpected_keyword}` in owner" + ), + self.get_current_token().span + ) } + None => match self.maybe_parse(|parser| parser.parse_identifier())? { + Some(ident) => Owner::Ident(ident), + None => { + return self.expected_ref( + "CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO", + self.peek_token_ref(), + ) + } + }, }; Ok(owner) } @@ -7323,9 +7336,7 @@ Some(match keyword { Keyword::PERMISSIVE => CreatePolicyType::Permissive, Keyword::RESTRICTIVE => CreatePolicyType::Restrictive, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in policy type"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in policy type"), self.get_current_token().span), }) } else { None @@ -7345,9 +7356,7 @@ Keyword::INSERT => CreatePolicyCommand::Insert, Keyword::UPDATE => CreatePolicyCommand::Update, Keyword::DELETE => CreatePolicyCommand::Delete, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in policy command"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in policy command"), self.get_current_token().span), }) } else { None @@ -7541,10 +7550,10 @@ options.push(OperatorOption::Join(Some(self.parse_object_name(false)?))); } _ => { - return Err(ParserError::ParserError(format!( - "Duplicate or unexpected keyword {:?} in CREATE OPERATOR", - keyword - ))) + return parser_err!( + format!("Duplicate or unexpected keyword {keyword:?} in CREATE OPERATOR"), + self.get_current_token().span + ) } } @@ -7557,9 +7566,12 @@ self.expect_token(&Token::RParen)?; // FUNCTION is required - let function = function.ok_or_else(|| { - ParserError::ParserError("CREATE OPERATOR requires FUNCTION parameter".to_string()) - })?; + let Some(function) = function else { + return parser_err!( + "CREATE OPERATOR requires FUNCTION parameter", + self.get_current_token().span + ); + }; Ok(CreateOperator { name, @@ -7780,7 +7792,7 @@ let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let names = self.parse_comma_separated(|p| p.parse_object_name(false))?; - let loc = self.peek_token_ref().span.start; + let loc = self.peek_token_ref().span; let cascade = self.parse_keyword(Keyword::CASCADE); let restrict = self.parse_keyword(Keyword::RESTRICT); let purge = self.parse_keyword(Keyword::PURGE); @@ -7985,9 +7997,7 @@ match keyword { Keyword::WITH => Some(true), Keyword::WITHOUT => Some(false), - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in cursor hold"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in cursor hold"), self.get_current_token().span), } } None => None, @@ -8207,9 +8217,7 @@ Token::Word(w) if w.keyword == Keyword::CURSOR ) { - Err(ParserError::TokenizerError( - "Invalid MsSql variable declaration.".to_string(), - )) + parser_err!("Invalid MsSql variable declaration.", ident.span) } else { Ok(ident) } @@ -8348,7 +8356,7 @@ self.expect_keyword(Keyword::IN)?; FetchPosition::In } else { - return parser_err!("Expected FROM or IN", self.peek_token_ref().span.start); + return parser_err!("Expected FROM or IN", self.peek_token_ref().span); }; let name = self.parse_identifier()?; @@ -9169,7 +9177,7 @@ } else { parser_err!( "Expecting DELETE ROWS, PRESERVE ROWS or DROP", - self.peek_token_ref() + self.peek_token_ref().span ) } } @@ -10723,9 +10731,7 @@ Ok(SqlOption::Clustered(TableOptionsClustered::Index(columns))) } else { - Err(ParserError::ParserError( - "invalid CLUSTERED sequence".to_string(), - )) + parser_err!("invalid CLUSTERED sequence", self.peek_token_ref().span) } } @@ -11329,9 +11335,7 @@ Keyword::PART => Ok(Partition::Part(self.parse_expr()?)), Keyword::PARTITION => Ok(Partition::Expr(self.parse_expr()?)), // unreachable because expect_one_of_keywords used above - unexpected_keyword => Err(ParserError::ParserError( - format!("Internal parser error: expected any of {{PART, PARTITION}}, got {unexpected_keyword:?}"), - )), + unexpected_keyword => parser_err!(format!("Internal parser error: expected any of {{PART, PARTITION}}, got {unexpected_keyword:?}"), self.get_current_token().span), } } @@ -11408,9 +11412,7 @@ } Keyword::USER => self.parse_alter_user().map(Into::into), // unreachable because expect_one_of_keywords used above - unexpected_keyword => Err(ParserError::ParserError( - format!("Internal parser error: expected any of {{TEXT SEARCH, VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"), - )), + unexpected_keyword => parser_err!(format!("Internal parser error: expected any of {{TEXT SEARCH, VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"), self.get_current_token().span), } } @@ -11856,9 +11858,7 @@ Keyword::MERGES => { options.push(OperatorOption::Merges); } - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in operator option"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in operator option"), self.get_current_token().span), } if !self.consume_token(&Token::Comma) { @@ -12126,7 +12126,7 @@ Expr::Function(f) => Ok(Statement::Call(f)), other => parser_err!( format!("Expected a simple procedure call but found: {other}"), - self.peek_token_ref().span.start + self.peek_token_ref().span ), } } else { @@ -12166,9 +12166,10 @@ // Use a separate if statement to prevent Rust compiler from complaining about // "if statement in this position is unstable: https://github.com/rust-lang/rust/issues/53667" if let CopySource::Query(_) = source { - return Err(ParserError::ParserError( - "COPY ... FROM does not support query as a source".to_string(), - )); + return parser_err!( + "COPY ... FROM does not support query as a source", + self.get_current_token().span + ); } } let target = if self.parse_keyword(Keyword::STDIN) { @@ -12539,10 +12540,7 @@ fn parse_literal_char(&mut self) -> Result<char, ParserError> { let s = self.parse_literal_string()?; if s.len() != 1 { - let loc = self - .tokens - .get(self.index - 1) - .map_or(Location { line: 0, column: 0 }, |t| t.span.start); + let loc = self.get_current_token().span; return parser_err!(format!("Expect a char, found {s:?}"), loc); } Ok(s.chars().next().unwrap()) @@ -12620,7 +12618,7 @@ // The call to n.parse() returns a bigdecimal when the // bigdecimal feature is enabled, and is otherwise a no-op // (i.e., it returns the input string). - Token::Number(n, l) => ok_value(Value::Number(Self::parse(n, span.start)?, l)), + Token::Number(n, l) => ok_value(Value::Number(Self::parse(n, span)?, l)), Token::SingleQuotedString(ref s) => ok_value(Value::SingleQuotedString( self.maybe_concat_string_literal(s.to_string()), )), @@ -12683,7 +12681,10 @@ // 2. Not calling self.next_token() to enforce `tok` // be followed immediately by a word/number, ie. // without any whitespace in between - let next_token = self.next_token_no_skip().unwrap_or(&EOF_TOKEN).clone(); + let next_token = match self.next_token_no_skip() { + Some(token) => token.clone(), + None => self.eof_token.clone(), + }; let ident = match next_token.token { Token::Word(w) if w.quote_style.is_none() => Ok(w.into_ident(next_token.span)), Token::Number(w, false) => Ok(Ident::with_span(next_token.span, w)), @@ -12801,7 +12802,7 @@ pub fn parse_literal_uint(&mut self) -> Result<u64, ParserError> { let next_token = self.next_token(); match next_token.token { - Token::Number(s, _) => Self::parse::<u64>(s, next_token.span.start), + Token::Number(s, _) => Self::parse::<u64>(s, next_token.span), _ => self.expected("literal int", next_token), } } @@ -12950,7 +12951,7 @@ if trailing_bracket.0 { return parser_err!( format!("unmatched > after parsing data type {ty}"), - self.peek_token_ref() + self.peek_token_ref().span ); } @@ -12961,7 +12962,7 @@ fn parse_data_type_helper( &mut self, ) -> Result<(DataType, MatchedTrailingBracket), ParserError> { - let _guard = self.recursion_counter.try_decrease()?; + let _guard = self.recursion_guard()?; let dialect = self.dialect; self.advance_token(); @@ -13356,7 +13357,7 @@ if key_trailing_bracket.0 { return parser_err!( format!("unmatched > after parsing data type {key_data_type}"), - self.peek_token_ref() + self.peek_token_ref().span ); } self.expect_token(&Token::Comma)?; @@ -13581,12 +13582,14 @@ &mut self, operator_name: &str, ) -> Result<SetQuantifier, ParserError> { + let span = self.peek_token_ref().span; let quantifier = self.parse_set_quantifier(&Some(SetOperator::Intersect)); match quantifier { SetQuantifier::Distinct | SetQuantifier::DistinctByName => Ok(quantifier), - _ => Err(ParserError::ParserError(format!( - "{operator_name} pipe operator requires DISTINCT modifier", - ))), + _ => parser_err!( + format!("{operator_name} pipe operator requires DISTINCT modifier"), + span + ), } } @@ -13780,7 +13783,7 @@ _ => { return parser_err!( "BUG: expected to match GroupBy modifier keyword", - self.peek_token_ref().span.start + self.peek_token_ref().span ) } }); @@ -13853,11 +13856,12 @@ && expr.is_some() // ALL not supported with comma && self.consume_token(&Token::Comma) { - let offset = expr.ok_or_else(|| { - ParserError::ParserError( - "Missing offset for LIMIT <offset>, <limit>".to_string(), - ) - })?; + let Some(offset) = expr else { + return parser_err!( + "Missing offset for LIMIT <offset>, <limit>", + self.get_current_token().span + ); + }; return Ok(Some(LimitClause::OffsetCommaLimit { offset, limit: self.parse_expr()?, @@ -14065,7 +14069,7 @@ /// let actual = parser.parse_multipart_identifier().unwrap_err(); /// assert_eq!( /// actual.to_string(), - /// "sql parser error: Unexpected token in identifier: +" + /// "sql parser error: Unexpected token in identifier: + at Line: 1, Column: 5" /// ); /// ``` /// @@ -14078,14 +14082,13 @@ match next_token.token { Token::Word(w) => idents.push(w.into_ident(next_token.span)), Token::EOF => { - return Err(ParserError::ParserError( - "Empty input when parsing identifier".to_string(), - ))? + return parser_err!("Empty input when parsing identifier", next_token.span) } token => { - return Err(ParserError::ParserError(format!( - "Unexpected token in identifier: {token}" - )))? + return parser_err!( + format!("Unexpected token in identifier: {token}"), + next_token.span + ) } }; @@ -14098,22 +14101,22 @@ match next_token.token { Token::Word(w) => idents.push(w.into_ident(next_token.span)), Token::EOF => { - return Err(ParserError::ParserError( - "Trailing period in identifier".to_string(), - ))? + return parser_err!("Trailing period in identifier", next_token.span) } token => { - return Err(ParserError::ParserError(format!( - "Unexpected token following period in identifier: {token}" - )))? + return parser_err!( + format!("Unexpected token following period in identifier: {token}"), + next_token.span + ) } } } Token::EOF => break, token => { - Err(ParserError::ParserError(format!( - "Unexpected token in identifier: {token}" - )))?; + return parser_err!( + format!("Unexpected token in identifier: {token}"), + self.get_current_token().span + ); } } } @@ -14574,9 +14577,9 @@ match ¤t_token.token { Token::Number(s, _) => { let s = s.clone(); - let span_start = current_token.span.start; + let span = current_token.span; self.advance_token(); - let value = Self::parse::<i64>(s, span_start)?; + let value = Self::parse::<i64>(s, span)?; Ok(if is_negative { -value } else { value }) } _ => self.expected_ref("number", current_token), @@ -14787,10 +14790,11 @@ } } + let statement_span = self.peek_token_ref().span; match self.maybe_parse(|parser| parser.parse_statement())? { - Some(Statement::Explain { .. }) | Some(Statement::ExplainTable { .. }) => Err( - ParserError::ParserError("Explain must be root of the plan".to_string()), - ), + Some(Statement::Explain { .. }) | Some(Statement::ExplainTable { .. }) => { + parser_err!("Explain must be root of the plan", statement_span) + } Some(statement) => Ok(Statement::Explain { describe_alias, analyze, @@ -14833,7 +14837,7 @@ /// expect the initial keyword to be already consumed #[cfg_attr(feature = "recursive-protection", recursive::recursive)] pub fn parse_query(&mut self) -> Result<Box<Query>, ParserError> { - let _guard = self.recursion_counter.try_decrease()?; + let _guard = self.recursion_guard()?; let with = if self.parse_keyword(Keyword::WITH) { let with_token = self.get_current_token(); Some(With { @@ -15100,9 +15104,10 @@ let alias = self.parse_identifier_optional_alias()?; pipe_operators.push(PipeOperator::Call { function, alias }); } else { - return Err(ParserError::ParserError( - "Expected function call after CALL".to_string(), - )); + return parser_err!( + "Expected function call after CALL", + function_expr.span() + ); } } Keyword::PIVOT => { @@ -15169,19 +15174,19 @@ | Keyword::FULL | Keyword::CROSS => { self.prev_token(); + let join_span = self.peek_token_ref().span; let mut joins = self.parse_joins()?; if joins.len() != 1 { - return Err(ParserError::ParserError( - "Join pipe operator must have a single join".to_string(), - )); + return parser_err!("Join pipe operator must have a single join", join_span); } let join = joins.swap_remove(0); pipe_operators.push(PipeOperator::Join(join)) } unhandled => { - return Err(ParserError::ParserError(format!( - "`expect_one_of_keywords` further up allowed unhandled keyword: {unhandled:?}" - ))) + return parser_err!( + format!("`expect_one_of_keywords` further up allowed unhandled keyword: {unhandled:?}"), + self.get_current_token().span + ) } } } @@ -15240,9 +15245,10 @@ } ForXml::Path(element_name) } else { - return Err(ParserError::ParserError( - "Expected FOR XML [RAW | AUTO | EXPLICIT | PATH ]".to_string(), - )); + return self.expected_ref( + "RAW, AUTO, EXPLICIT or PATH after FOR XML", + self.peek_token_ref(), + ); }; let mut elements = false; let mut binary_base64 = false; @@ -15279,9 +15285,7 @@ } else if self.parse_keyword(Keyword::PATH) { ForJson::Path } else { - return Err(ParserError::ParserError( - "Expected FOR JSON [AUTO | PATH ]".to_string(), - )); + return self.expected_ref("AUTO or PATH after FOR JSON", self.peek_token_ref()); }; let mut root = None; let mut include_null_values = false; @@ -16125,10 +16129,13 @@ return if assignments.len() > 1 { Ok(Set::MultipleAssignments { assignments }.into()) } else { - let SetAssignment { scope, name, value } = - assignments.into_iter().next().ok_or_else(|| { - ParserError::ParserError("Expected at least one assignment".to_string()) - })?; + let Some(SetAssignment { scope, name, value }) = assignments.into_iter().next() + else { + return parser_err!( + "Expected at least one assignment", + self.get_current_token().span + ); + }; Ok(Set::SingleAssignment { scope, @@ -16280,9 +16287,10 @@ } else if self.parse_keyword(Keyword::PROCESSLIST) { Ok(Statement::ShowProcessList { full }) } else if extended || full { - Err(ParserError::ParserError( - "EXTENDED/FULL are not supported with this type of SHOW query".to_string(), - )) + parser_err!( + "EXTENDED/FULL are not supported with this type of SHOW query", + self.peek_token_ref().span + ) } else if self.parse_one_of_keywords(&[Keyword::CREATE]).is_some() { Ok(self.parse_show_create()?) } else if self.parse_keyword(Keyword::COLLATION) { @@ -16374,9 +16382,10 @@ Keyword::PROCEDURE => Ok(ShowCreateObject::Procedure), Keyword::EVENT => Ok(ShowCreateObject::Event), Keyword::VIEW => Ok(ShowCreateObject::View), - keyword => Err(ParserError::ParserError(format!( - "Unable to map keyword to ShowCreateObject: {keyword:?}" - ))), + keyword => parser_err!( + format!("Unable to map keyword to ShowCreateObject: {keyword:?}"), + self.get_current_token().span + ), }?; let obj_name = self.parse_object_name(false)?; @@ -16663,9 +16672,10 @@ } } _ => { - return Err(ParserError::ParserError(format!( - "expected OUTER, SEMI, ANTI or JOIN after {kw:?}" - ))) + return self.expected_ref( + &format!("OUTER, SEMI, ANTI or JOIN after {kw:?}"), + self.peek_token_ref(), + ) } } } @@ -16742,7 +16752,7 @@ /// A table name or a parenthesized subquery, followed by optional `[AS] alias` #[cfg_attr(feature = "recursive-protection", recursive::recursive)] pub fn parse_table_factor(&mut self) -> Result<TableFactor, ParserError> { - let _guard = self.recursion_counter.try_decrease()?; + let _guard = self.recursion_guard()?; if self.parse_keyword(Keyword::LATERAL) { // LATERAL must always be followed by a subquery or table function. if self.consume_token(&Token::LParen) { @@ -16816,9 +16826,7 @@ table = match kw { Keyword::PIVOT => self.parse_pivot_table_factor(table)?, Keyword::UNPIVOT => self.parse_unpivot_table_factor(table)?, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in pivot/unpivot"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in pivot/unpivot"), self.get_current_token().span), } } return Ok(table); @@ -16880,9 +16888,10 @@ | TableFactor::NestedJoin { alias, .. } => { // but not `FROM (mytable AS alias1) AS alias2`. if let Some(inner_alias) = alias { - return Err(ParserError::ParserError(format!( - "duplicate alias {inner_alias}" - ))); + return parser_err!( + format!("duplicate alias {inner_alias}"), + outer_alias.name.span + ); } // Act as if the alias was specified normally next // to the table name: `(mytable) AS alias` -> @@ -16890,10 +16899,10 @@ alias.replace(outer_alias); } TableFactor::UnpivotExpr { .. } => { - return Err(ParserError::ParserError( - "alias after parenthesized UNPIVOT expression is not supported" - .to_string(), - )) + return parser_err!( + "alias after parenthesized UNPIVOT expression is not supported", + outer_alias.name.span + ) } }; } @@ -17087,9 +17096,7 @@ table = match kw { Keyword::PIVOT => self.parse_pivot_table_factor(table)?, Keyword::UNPIVOT => self.parse_unpivot_table_factor(table)?, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in pivot/unpivot"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in pivot/unpivot"), self.get_current_token().span), } } @@ -17191,7 +17198,7 @@ } else { return parser_err!( "Expecting number or byte length e.g. 100M", - self.peek_token_ref().span.start + self.peek_token_ref().span ); } } @@ -17387,30 +17394,34 @@ while self.peek_token_ref().token != Token::RParen { if self.parse_keyword(Keyword::DIMENSIONS) { if !dimensions.is_empty() { - return Err(ParserError::ParserError( - "DIMENSIONS clause can only be specified once".to_string(), - )); + return parser_err!( + "DIMENSIONS clause can only be specified once", + self.get_current_token().span + ); } dimensions = self.parse_comma_separated(Parser::parse_wildcard_expr)?; } else if self.parse_keyword(Keyword::METRICS) { if !metrics.is_empty() { - return Err(ParserError::ParserError( - "METRICS clause can only be specified once".to_string(), - )); + return parser_err!( + "METRICS clause can only be specified once", + self.get_current_token().span + ); } metrics = self.parse_comma_separated(Parser::parse_wildcard_expr)?; } else if self.parse_keyword(Keyword::FACTS) { if !facts.is_empty() { - return Err(ParserError::ParserError( - "FACTS clause can only be specified once".to_string(), - )); + return parser_err!( + "FACTS clause can only be specified once", + self.get_current_token().span + ); } facts = self.parse_comma_separated(Parser::parse_wildcard_expr)?; } else if self.parse_keyword(Keyword::WHERE) { if where_clause.is_some() { - return Err(ParserError::ParserError( - "WHERE clause can only be specified once".to_string(), - )); + return parser_err!( + "WHERE clause can only be specified once", + self.get_current_token().span + ); } where_clause = Some(self.parse_expr()?); } else { @@ -17420,7 +17431,7 @@ "Expected one of DIMENSIONS, METRICS, FACTS or WHERE, got {}", tok.token ), - tok.span.start + tok.span )?; } } @@ -17600,7 +17611,7 @@ return self.expected("literal number", next_token); }; self.expect_token(&Token::RBrace)?; - RepetitionQuantifier::AtMost(Self::parse(n, token.span.start)?) + RepetitionQuantifier::AtMost(Self::parse(n, token.span)?) } Token::Number(n, _) if self.consume_token(&Token::Comma) => { let next_token = self.next_token(); @@ -17608,19 +17619,19 @@ Token::Number(m, _) => { self.expect_token(&Token::RBrace)?; RepetitionQuantifier::Range( - Self::parse(n, token.span.start)?, - Self::parse(m, token.span.start)?, + Self::parse(n, token.span)?, + Self::parse(m, token.span)?, ) } Token::RBrace => { - RepetitionQuantifier::AtLeast(Self::parse(n, token.span.start)?) + RepetitionQuantifier::AtLeast(Self::parse(n, token.span)?) } _ => return self.expected("} or upper bound", next_token), } } Token::Number(n, _) => { self.expect_token(&Token::RBrace)?; - RepetitionQuantifier::Exactly(Self::parse(n, token.span.start)?) + RepetitionQuantifier::Exactly(Self::parse(n, token.span)?) } _ => return self.expected("quantifier range", token), } @@ -18311,9 +18322,7 @@ } } Some(Keyword::TABLE) | None => Some(GrantObjects::Tables(objects?)), - Some(unexpected_keyword) => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in grant objects"), - )), + Some(unexpected_keyword) => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in grant objects"), self.get_current_token().span), } } } else { @@ -18626,7 +18635,7 @@ None => { return parser_err!( "DENY statements must specify an object", - self.peek_token_ref().span.start + self.peek_token_ref().span ) } }; @@ -18683,10 +18692,7 @@ replace_token: TokenWithSpan, ) -> Result<Statement, ParserError> { if !dialect_of!(self is MySqlDialect | GenericDialect) { - return parser_err!( - "Unsupported statement REPLACE", - self.peek_token_ref().span.start - ); + return parser_err!("Unsupported statement REPLACE", self.peek_token_ref().span); } let mut insert = self.parse_insert(replace_token)?; @@ -19373,9 +19379,7 @@ let kind = match self.expect_one_of_keywords(&[Keyword::MIN, Keyword::MAX])? { Keyword::MIN => HavingBoundKind::Min, Keyword::MAX => HavingBoundKind::Max, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in having bound"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: unexpected keyword `{unexpected_keyword}` in having bound"), self.get_current_token().span), }; clauses.push(FunctionArgumentClause::Having(HavingBound( kind, @@ -19433,7 +19437,7 @@ } fn parse_duplicate_treatment(&mut self) -> Result<Option<DuplicateTreatment>, ParserError> { - let loc = self.peek_token_ref().span.start; + let loc = self.peek_token_ref().span; match ( self.parse_keyword(Keyword::ALL), self.parse_keyword(Keyword::DISTINCT), @@ -19465,7 +19469,7 @@ Expr::Identifier(v) if v.value.to_lowercase() == "from" && v.quote_style.is_none() => { parser_err!( format!("Expected an expression, found: {}", v), - self.peek_token_ref().span.start + self.peek_token_ref().span ) } Expr::BinaryOp { @@ -19479,13 +19483,13 @@ { return parser_err!( format!("Expected an expression, found: {right}"), - self.peek_token_ref().span.start + self.peek_token_ref().span ); } let Expr::Identifier(alias) = *left else { return parser_err!( "BUG: expected identifier expression as alias", - self.peek_token_ref().span.start + self.peek_token_ref().span ); }; Ok(SelectItem::ExprWithAlias { @@ -19894,7 +19898,7 @@ } else { let next_token = self.next_token(); let quantity = match next_token.token { - Token::Number(s, _) => Self::parse::<u64>(s, next_token.span.start)?, + Token::Number(s, _) => Self::parse::<u64>(s, next_token.span)?, _ => self.expected("literal int", next_token)?, }; Some(TopQuantity::Constant(quantity)) @@ -19967,9 +19971,7 @@ let lock_type = match self.expect_one_of_keywords(&[Keyword::UPDATE, Keyword::SHARE])? { Keyword::UPDATE => LockType::Update, Keyword::SHARE => LockType::Share, - unexpected_keyword => return Err(ParserError::ParserError( - format!("Internal parser error: expected any of {{UPDATE, SHARE}}, got {unexpected_keyword:?}"), - )), + unexpected_keyword => return parser_err!(format!("Internal parser error: expected any of {{UPDATE, SHARE}}, got {unexpected_keyword:?}"), self.get_current_token().span), }; let of = if self.parse_keyword(Keyword::OF) { Some(self.parse_object_name(false)?) @@ -21186,11 +21188,12 @@ Some(Keyword::CATEGORY) => { self.expect_token(&Token::Eq)?; let category_str = self.parse_literal_string()?; - let category_char = category_str.chars().next().ok_or_else(|| { - ParserError::ParserError( - "CATEGORY value must be a single character".to_string(), - ) - })?; + let Some(category_char) = category_str.chars().next() else { + return parser_err!( + "CATEGORY value must be a single character", + self.get_current_token().span + ); + }; Ok(UserDefinedTypeSqlDefinitionOption::Category(category_char)) } Some(Keyword::PREFERRED) => { @@ -22245,22 +22248,21 @@ let ast = Parser::parse_sql(&GenericDialect, sql); assert_eq!( ast, - Err(ParserError::TokenizerError( - "Unterminated string literal at Line: 1, Column: 5".to_string() - )) + Err(ParserError::TokenizerError { + message: "Unterminated string literal".to_string(), + span: Span::new(Location::new(1, 5), Location::new(1, 5)), + }) ); } #[test] fn test_parser_error_loc() { let sql = "SELECT this is a syntax error"; - let ParserError::ParserError(msg) = Parser::parse_sql(&GenericDialect, sql).unwrap_err() - else { - panic!("expected ParserError::ParserError"); - }; + let err = Parser::parse_sql(&GenericDialect, sql).unwrap_err(); + assert!(matches!(err, ParserError::ParserError { .. })); assert!( - msg.ends_with("found: a at Line: 1, Column: 16"), - "unexpected error message: {msg}" + err.to_string().ends_with("found: a at Line: 1, Column: 16"), + "unexpected error message: {err}" ); } @@ -22270,9 +22272,10 @@ let ast = Parser::parse_sql(&GenericDialect, sql); assert_eq!( ast, - Err(ParserError::ParserError( - "Explain must be root of the plan".to_string() - )) + Err(ParserError::ParserError { + message: "Explain must be root of the plan".to_string(), + span: Span::new(Location::new(1, 9), Location::new(1, 16)), + }) ); } @@ -22335,27 +22338,27 @@ test_parse_multipart_identifier_error!( "", - "sql parser error: Empty input when parsing identifier", + "sql parser error: Empty input when parsing identifier at Line: 1, Column: 1", ); test_parse_multipart_identifier_error!( "*schema.table", - "sql parser error: Unexpected token in identifier: *", + "sql parser error: Unexpected token in identifier: * at Line: 1, Column: 1", ); test_parse_multipart_identifier_error!( "schema.table*", - "sql parser error: Unexpected token in identifier: *", + "sql parser error: Unexpected token in identifier: * at Line: 1, Column: 13", ); test_parse_multipart_identifier_error!( "schema.table.", - "sql parser error: Trailing period in identifier", + "sql parser error: Trailing period in identifier at Line: 1, Column: 14", ); test_parse_multipart_identifier_error!( "schema.*", - "sql parser error: Unexpected token following period in identifier: *", + "sql parser error: Unexpected token following period in identifier: * at Line: 1, Column: 8", ); } @@ -22389,9 +22392,10 @@ let sql = "REPLACE INTO t (a) VALUES (&a)"; assert_eq!( - ParserError::ParserError( - "Expected: an expression, found: & at Line: 1, Column: 28".to_string() - ), + ParserError::ParserError { + message: "Expected: an expression, found: &".to_string(), + span: Span::new(Location::new(1, 28), Location::new(1, 29)), + }, Parser::parse_sql(&GenericDialect {}, sql).unwrap_err() ); } @@ -22401,10 +22405,11 @@ let sql = "REPLACE INTO t SET ?"; assert_eq!( - ParserError::ParserError( - "Expected: SELECT, VALUES, or a subquery in the query body, found: SET at Line: 1, Column: 16" - .to_string() - ), + ParserError::ParserError { + message: "Expected: SELECT, VALUES, or a subquery in the query body, found: SET" + .to_string(), + span: Span::new(Location::new(1, 16), Location::new(1, 19)), + }, Parser::parse_sql(&GenericDialect {}, sql).unwrap_err() ); } @@ -22414,26 +22419,30 @@ let sql = r#"REPLACE"#; assert_eq!( - ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + ParserError::ParserError { + message: "Expected: identifier, found: EOF".to_string(), + span: Span::new(Location::new(1, 8), Location::new(1, 8)), + }, Parser::parse_sql(&MySqlDialect {}, sql).unwrap_err() ); } #[test] fn test_placeholder_invalid_whitespace() { - for (w, expected) in [ - ( - " ", - "Expected: placeholder, found: at Line: 3, Column: 4", - ), + for (w, expected, end_column) in [ + (" ", "Expected: placeholder, found: ", 5), ( "/*invalid*/", - "Expected: placeholder, found: /*invalid*/ at Line: 3, Column: 4", + "Expected: placeholder, found: /*invalid*/", + 15, ), ] { let sql = format!("\nSELECT\n :{w}fooBar"); assert_eq!( - ParserError::ParserError(expected.to_string()), + ParserError::ParserError { + message: expected.to_string(), + span: Span::new(Location::new(3, 4), Location::new(3, end_column)), + }, Parser::parse_sql(&GenericDialect, &sql).unwrap_err() ); }
diff --git a/src/test_utils.rs b/src/test_utils.rs index c4d1d0d..14c35e8 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs
@@ -33,7 +33,7 @@ use crate::dialect::*; use crate::parser::{Parser, ParserError}; -use crate::tokenizer::{Token, Tokenizer}; +use crate::tokenizer::{Span, Token, Tokenizer}; use crate::{ast::*, parser::ParserOptions}; #[cfg(test)] @@ -135,6 +135,18 @@ // Parser::parse_sql(&**self.dialects.first().unwrap(), sql) } + /// Parses statements while preserving source locations. + pub fn parse_sql_statements_with_locations( + &self, + sql: &str, + ) -> Result<Vec<Statement>, ParserError> { + self.one_of_identical_results(|dialect| { + self.new_parser(dialect) + .try_with_sql(sql)? + .parse_statements() + }) + } + /// Ensures that `sql` parses as a single [Statement] for all tested /// dialects. /// @@ -370,6 +382,14 @@ Value::SingleQuotedString(s.into()) } +/// Creates a parser error without a source location. +pub fn parser_error(message: impl Into<String>) -> ParserError { + ParserError::ParserError { + message: message.into(), + span: Span::empty(), + } +} + pub fn table_alias(explicit: bool, name: impl Into<String>) -> Option<TableAlias> { Some(TableAlias { explicit,
diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 4e76be8..3906a28 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs
@@ -777,7 +777,8 @@ } /// An error reported by the tokenizer, with a human-readable `message` and a `location`. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[error("{message}{location}")] pub struct TokenizerError { /// A descriptive error message. pub message: String, @@ -785,14 +786,6 @@ pub location: Location, } -impl fmt::Display for TokenizerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}{}", self.message, self.location,) - } -} - -impl core::error::Error for TokenizerError {} - struct State<'a> { peekable: Peekable<Chars<'a>>, line: u64,
diff --git a/tests/no_std_recursion.rs b/tests/no_std_recursion.rs index e69e767..13dd9d3 100644 --- a/tests/no_std_recursion.rs +++ b/tests/no_std_recursion.rs
@@ -28,7 +28,10 @@ .unwrap() .parse_statements(); - assert_eq!(result, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + result, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -42,7 +45,10 @@ let result = Parser::parse_sql(&dialect, &sql); - assert_eq!(result, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + result, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -55,10 +61,8 @@ // Deep nesting errors with a generic "expected end of statement" error // rather than `RecursionLimitExceeded`, instead of overflowing the stack. assert_eq!( - result, - Err(ParserError::ParserError( - "Expected: end of statement, found: NOT at Line: 1, Column: 211".to_string() - )) + result.unwrap_err().to_string(), + "sql parser error: Expected: end of statement, found: NOT at Line: 1, Column: 211" ); } @@ -91,7 +95,10 @@ let result = Parser::parse_sql(&dialect, &sql); - assert_eq!(result, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + result, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -105,7 +112,10 @@ let result = Parser::parse_sql(&dialect, &sql); - assert_eq!(result, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + result, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -115,5 +125,8 @@ let result = Parser::parse_sql(&dialect, &sql); - assert_eq!(result, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + result, + Err(ParserError::RecursionLimitExceeded { .. }) + )); }
diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index daebe63..38207a3 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs
@@ -24,7 +24,7 @@ use sqlparser::ast::*; use sqlparser::dialect::{BigQueryDialect, GenericDialect}; use sqlparser::keywords::Keyword; -use sqlparser::parser::{ParserError, ParserOptions}; +use sqlparser::parser::ParserOptions; use sqlparser::tokenizer::{Location, Span, Token, TokenWithSpan, Word}; use test_utils::*; @@ -295,13 +295,13 @@ bigquery() .parse_sql_statements("BEGIN SELECT 1; SELECT 2 END") .unwrap_err(), - ParserError::ParserError("Expected: ;, found: END".to_string()) + parser_error("Expected: ;, found: END".to_string()) ); assert_eq!( bigquery() .parse_sql_statements("BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2 END") .unwrap_err(), - ParserError::ParserError("Expected: ;, found: END".to_string()) + parser_error("Expected: ;, found: END".to_string()) ); } @@ -1579,7 +1579,7 @@ let sql = format!("SELECT 1 FROM {ident}"); assert_eq!( bigquery().parse_sql_statements(&sql), - Err(ParserError::ParserError(expected.to_string())) + Err(parser_error(expected.to_string())) ); } @@ -1776,8 +1776,8 @@ let sql = "SELECT 1 FROM t1 FOR SYSTEM TIME AS OF 'some_timestamp'".to_string(); assert_eq!( bigquery().parse_sql_statements(&sql), - Err(ParserError::ParserError( - "Expected: one of UPDATE or SHARE, found: SYSTEM".to_string() + Err(parser_error( + "Expected: one of UPDATE or SHARE, found: SYSTEM" )) ); } @@ -2084,10 +2084,7 @@ ), ] { let res = dialects.parse_sql_statements(sql); - assert_eq!( - ParserError::ParserError(err_msg.to_string()), - res.unwrap_err() - ); + assert_eq!(parser_error(err_msg.to_string()), res.unwrap_err()); } } @@ -2209,13 +2206,13 @@ let error_sql = "DECLARE x"; assert_eq!( - ParserError::ParserError("Expected: a data type name, found: EOF".to_owned()), + parser_error("Expected: a data type name, found: EOF".to_owned()), bigquery().parse_sql_statements(error_sql).unwrap_err() ); let error_sql = "DECLARE x 42"; assert_eq!( - ParserError::ParserError("Expected: a data type name, found: 42".to_owned()), + parser_error("Expected: a data type name, found: 42".to_owned()), bigquery().parse_sql_statements(error_sql).unwrap_err() ); } @@ -2446,7 +2443,7 @@ ]; for (sql, error) in error_sqls { assert_eq!( - ParserError::ParserError(error.to_owned()), + parser_error(error.to_owned()), bigquery().parse_sql_statements(sql).unwrap_err() ); } @@ -2476,7 +2473,7 @@ // missing comma separation let error_sql = "SELECT TRIM('xyz' 'a')"; assert_eq!( - ParserError::ParserError("Expected: ), found: 'a'".to_owned()), + parser_error("Expected: ), found: 'a'".to_owned()), bigquery().parse_sql_statements(error_sql).unwrap_err() ); } @@ -2639,7 +2636,7 @@ // Bad case with missing closing bracket assert_eq!( - ParserError::ParserError("Expected: >, found: )".to_owned()), + parser_error("Expected: >, found: )".to_owned()), bigquery() .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT<a STRING, b INT64)") .unwrap_err() @@ -2647,9 +2644,7 @@ // Bad case with redundant closing bracket assert_eq!( - ParserError::ParserError( - "unmatched > after parsing data type STRUCT<a STRING, b INT64>)".to_owned() - ), + parser_error("unmatched > after parsing data type STRUCT<a STRING, b INT64>".to_owned()), bigquery() .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT<a STRING, b INT64>>)") .unwrap_err() @@ -2657,9 +2652,7 @@ // Base case with redundant closing bracket in nested struct assert_eq!( - ParserError::ParserError( - "Expected: ',' or ')' after column definition, found: >".to_owned() - ), + parser_error("Expected: ',' or ')' after column definition, found: >".to_owned()), bigquery() .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT<a STRUCT<b INT>>>, c INT64)") .unwrap_err() @@ -2670,7 +2663,7 @@ bigquery_and_generic() .parse_sql_statements(sql) .unwrap_err(), - ParserError::ParserError("unmatched > in STRUCT literal".to_string()) + parser_error("unmatched > in STRUCT literal".to_string()) ); let sql = "SELECT STRUCT<STRUCT<INT64>>>(NULL)"; @@ -2678,7 +2671,7 @@ bigquery_and_generic() .parse_sql_statements(sql) .unwrap_err(), - ParserError::ParserError("Expected: (, found: >".to_string()) + parser_error("Expected: (, found: >".to_string()) ); let sql = "CREATE TABLE table (x STRUCT<STRUCT<INT64>>>)"; @@ -2686,9 +2679,7 @@ bigquery_and_generic() .parse_sql_statements(sql) .unwrap_err(), - ParserError::ParserError( - "Expected: ',' or ')' after column definition, found: >".to_string() - ) + parser_error("Expected: ',' or ')' after column definition, found: >".to_string()) ); } @@ -2995,9 +2986,7 @@ .parse_sql_statements("SELECT B'unterminated") .unwrap_err(); assert_eq!( - ParserError::TokenizerError( - "Unterminated string literal at Line: 1, Column: 9".to_string(), - ), - err + err.to_string(), + "sql parser error: Unterminated string literal at Line: 1, Column: 9" ); }
diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 189300d..a47aab5 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs
@@ -32,7 +32,6 @@ use sqlparser::ast::*; use sqlparser::dialect::ClickHouseDialect; use sqlparser::dialect::GenericDialect; -use sqlparser::parser::ParserError::ParserError; #[test] fn parse_map_access_expr() { @@ -350,13 +349,13 @@ clickhouse_and_generic() .parse_sql_statements(format!("ALTER TABLE t0 {operation} PARTITION").as_str()) .unwrap_err(), - ParserError("Expected: an expression, found: EOF".to_string()) + parser_error("Expected: an expression, found: EOF".to_string()) ); assert_eq!( clickhouse_and_generic() .parse_sql_statements(format!("ALTER TABLE t0 {operation} PART").as_str()) .unwrap_err(), - ParserError("Expected: an expression, found: EOF".to_string()) + parser_error("Expected: an expression, found: EOF".to_string()) ); } } @@ -419,19 +418,19 @@ clickhouse_and_generic() .parse_sql_statements("ALTER TABLE t0 ADD PROJECTION my_name") .unwrap_err(), - ParserError("Expected: (, found: EOF".to_string()) + parser_error("Expected: (, found: EOF".to_string()) ); assert_eq!( clickhouse_and_generic() .parse_sql_statements("ALTER TABLE t0 ADD PROJECTION my_name ()") .unwrap_err(), - ParserError("Expected: SELECT, found: )".to_string()) + parser_error("Expected: SELECT, found: )".to_string()) ); assert_eq!( clickhouse_and_generic() .parse_sql_statements("ALTER TABLE t0 ADD PROJECTION my_name (SELECT)") .unwrap_err(), - ParserError("Expected: an expression, found: )".to_string()) + parser_error("Expected: an expression, found: )".to_string()) ); } @@ -461,7 +460,7 @@ clickhouse_and_generic() .parse_sql_statements("ALTER TABLE t0 DROP PROJECTION") .unwrap_err(), - ParserError("Expected: identifier, found: EOF".to_string()) + parser_error("Expected: identifier, found: EOF".to_string()) ); } @@ -508,7 +507,7 @@ clickhouse_and_generic() .parse_sql_statements(format!("ALTER TABLE t0 {keyword} PROJECTION",).as_str()) .unwrap_err(), - ParserError("Expected: identifier, found: EOF".to_string()) + parser_error("Expected: identifier, found: EOF".to_string()) ); assert_eq!( @@ -517,7 +516,7 @@ format!("ALTER TABLE t0 {keyword} PROJECTION my_name IN PARTITION",).as_str() ) .unwrap_err(), - ParserError("Expected: identifier, found: EOF".to_string()) + parser_error("Expected: identifier, found: EOF".to_string()) ); assert_eq!( @@ -526,7 +525,7 @@ format!("ALTER TABLE t0 {keyword} PROJECTION my_name IN",).as_str() ) .unwrap_err(), - ParserError("Expected: end of statement, found: IN".to_string()) + parser_error("Expected: end of statement, found: IN".to_string()) ); } } @@ -574,19 +573,19 @@ clickhouse_and_generic() .parse_sql_statements("OPTIMIZE TABLE t0 DEDUPLICATE BY") .unwrap_err(), - ParserError("Expected: an expression, found: EOF".to_string()) + parser_error("Expected: an expression, found: EOF".to_string()) ); assert_eq!( clickhouse_and_generic() .parse_sql_statements("OPTIMIZE TABLE t0 PARTITION") .unwrap_err(), - ParserError("Expected: an expression, found: EOF".to_string()) + parser_error("Expected: an expression, found: EOF".to_string()) ); assert_eq!( clickhouse_and_generic() .parse_sql_statements("OPTIMIZE TABLE t0 PARTITION ID") .unwrap_err(), - ParserError("Expected: identifier, found: EOF".to_string()) + parser_error("Expected: identifier, found: EOF".to_string()) ); } @@ -785,7 +784,7 @@ clickhouse() .parse_sql_statements("CREATE TABLE t (a ARRAY<INT>)") .unwrap_err(), - ParserError("Expected: (, found: <".to_string()) + parser_error("Expected: (, found: <") ); } @@ -1135,7 +1134,7 @@ clickhouse_and_generic() .parse_sql_statements(sql) .unwrap_err(), - ParserError(error_msg.to_string()) + parser_error(error_msg.to_string()) ); } } @@ -1640,7 +1639,7 @@ clickhouse_and_generic() .parse_sql_statements(format!("ALTER TABLE t0 {operation_name} PARTITION").as_str()) .unwrap_err(), - ParserError("Expected: an expression, found: EOF".to_string()) + parser_error("Expected: an expression, found: EOF".to_string()) ); assert_eq!( clickhouse_and_generic() @@ -1648,7 +1647,7 @@ format!("ALTER TABLE t0 {operation_name} PARTITION p0 WITH").as_str() ) .unwrap_err(), - ParserError("Expected: NAME, found: EOF".to_string()) + parser_error("Expected: NAME, found: EOF".to_string()) ); assert_eq!( clickhouse_and_generic() @@ -1656,7 +1655,7 @@ format!("ALTER TABLE t0 {operation_name} PARTITION p0 WITH NAME").as_str() ) .unwrap_err(), - ParserError("Expected: identifier, found: EOF".to_string()) + parser_error("Expected: identifier, found: EOF".to_string()) ); } } @@ -1914,7 +1913,7 @@ assert_eq!( clickhouse().parse_sql_statements("ALTER TABLE t ADD COLUMN c UInt8 AFTER"), - Err(ParserError("Expected: identifier, found: EOF".to_string())) + Err(parser_error("Expected: identifier, found: EOF".to_string())) ); } @@ -1967,7 +1966,7 @@ ] { assert_eq!( clickhouse_and_generic().parse_sql_statements(sql), - Err(ParserError(err.to_string())), + Err(parser_error(err.to_string())), "{sql}" ); } @@ -2091,13 +2090,13 @@ assert_eq!( clickhouse().parse_sql_statements("SELECT t."), - Err(ParserError( + Err(parser_error( "Expected: an identifier or a '*' after '.', found: EOF".to_string() )) ); assert_eq!( clickhouse().parse_sql_statements("SELECT t.1."), - Err(ParserError("Expected: identifier, found: EOF".to_string())) + Err(parser_error("Expected: identifier, found: EOF".to_string())) ); all_dialects_where(|d| !d.supports_tuple_element_access()).verified_stmt("SELECT (1, 'a') . 1"); @@ -2110,7 +2109,7 @@ !d.supports_tuple_element_access() && d.supports_numeric_prefix() == numeric_prefix }) .parse_sql_statements("SELECT t.1 FROM t"), - Err(ParserError(err.to_string())) + Err(parser_error(err.to_string())) ); } } @@ -2160,7 +2159,7 @@ assert_eq!( clickhouse().parse_sql_statements("SELECT * FROM t WHERE x GLOBAL y"), - Err(ParserError( + Err(parser_error( "Expected: end of statement, found: GLOBAL".to_string() )) ); @@ -2168,7 +2167,7 @@ assert_eq!( all_dialects_where(|d| !d.supports_global_in()) .parse_sql_statements("SELECT * FROM t WHERE x GLOBAL IN (1)"), - Err(ParserError( + Err(parser_error( "Expected: end of statement, found: GLOBAL".to_string() )) ); @@ -2294,14 +2293,14 @@ clickhouse_and_generic() .parse_sql_statements(sql) .unwrap_err(), - ParserError(err.to_string()) + parser_error(err.to_string()) ); } assert_eq!( all_dialects_where(|d| !d.supports_settings()) .parse_sql_statements("DELETE FROM hits WHERE x = 1 SETTINGS a = 1") .unwrap_err(), - ParserError("Expected: end of statement, found: SETTINGS".to_string()) + parser_error("Expected: end of statement, found: SETTINGS".to_string()) ); } @@ -2355,14 +2354,14 @@ clickhouse_and_generic() .parse_sql_statements(sql) .unwrap_err(), - ParserError(err.to_string()) + parser_error(err.to_string()) ); } assert_eq!( all_dialects_where(|d| !d.supports_settings()) .parse_sql_statements("UPDATE hits SET x = 1 WHERE y = 2 SETTINGS a = 1") .unwrap_err(), - ParserError("Expected: end of statement, found: SETTINGS".to_string()) + parser_error("Expected: end of statement, found: SETTINGS".to_string()) ); }
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 4d51989..21f1a99 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs
@@ -42,8 +42,8 @@ use sqlparser::tokenizer::{Location, Span, Token, Tokenizer, Whitespace}; use test_utils::{ all_dialects, all_dialects_where, all_dialects_with_options, alter_table_op, assert_eq_vec, - call, expr_from_projection, join, number, only, table, table_alias, table_from_name, - TestedDialects, + call, expr_from_projection, join, number, only, parser_error, table, table_alias, + table_from_name, TestedDialects, }; #[macro_use] @@ -206,8 +206,8 @@ let sql = "REPLACE INTO public.customer (id, name, active) VALUES (1, 2, 3)"; assert_eq!( - ParserError::ParserError("Unsupported statement REPLACE at Line: 1, Column: 9".to_string()), - Parser::parse_sql(&dialect, sql,).unwrap_err(), + Parser::parse_sql(&dialect, sql).unwrap_err().to_string(), + "sql parser error: Unsupported statement REPLACE at Line: 1, Column: 9", ) } @@ -298,7 +298,7 @@ let insert_with_columns_and_default_values = "INSERT INTO test_table (test_col) DEFAULT VALUES"; assert_eq!( - ParserError::ParserError( + parser_error( "Expected: SELECT, VALUES, or a subquery in the query body, found: DEFAULT".to_string() ), parse_sql_statements(insert_with_columns_and_default_values).unwrap_err() @@ -307,20 +307,20 @@ let insert_with_default_values_and_hive_after_columns = "INSERT INTO test_table DEFAULT VALUES (some_column)"; assert_eq!( - ParserError::ParserError("Expected: end of statement, found: (".to_string()), + parser_error("Expected: end of statement, found: (".to_string()), parse_sql_statements(insert_with_default_values_and_hive_after_columns).unwrap_err() ); let insert_with_default_values_and_hive_partition = "INSERT INTO test_table DEFAULT VALUES PARTITION (some_column)"; assert_eq!( - ParserError::ParserError("Expected: end of statement, found: PARTITION".to_string()), + parser_error("Expected: end of statement, found: PARTITION".to_string()), parse_sql_statements(insert_with_default_values_and_hive_partition).unwrap_err() ); let insert_with_default_values_and_values_list = "INSERT INTO test_table DEFAULT VALUES (1)"; assert_eq!( - ParserError::ParserError("Expected: end of statement, found: (".to_string()), + parser_error("Expected: end of statement, found: (".to_string()), parse_sql_statements(insert_with_default_values_and_values_list).unwrap_err() ); } @@ -453,14 +453,14 @@ let sql = "UPDATE t WHERE 1"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: SET, found: WHERE".to_string()), + parser_error("Expected: SET, found: WHERE".to_string()), res.unwrap_err() ); let sql = "UPDATE t SET a = 1 extrabadstuff"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: extrabadstuff".to_string()), + parser_error("Expected: end of statement, found: extrabadstuff".to_string()), res.unwrap_err() ); } @@ -723,9 +723,10 @@ parser.parse_object_name(false) }); assert_eq!( - ParserError::ParserError( - "Expected: identifier, found: . at Line: 1, Column: 11".to_string() - ), + ParserError::ParserError { + message: "Expected: identifier, found: .".to_string(), + span: Span::new(Location::new(1, 11), Location::new(1, 12)), + }, ast.unwrap_err() ); } @@ -734,7 +735,10 @@ fn parse_no_table_name() { let ast = all_dialects().run_parser_method("", |parser| parser.parse_object_name(false)); assert_eq!( - ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + ParserError::ParserError { + message: "Expected: identifier, found: EOF".to_string(), + span: Span::new(Location::new(1, 1), Location::new(1, 1)), + }, ast.unwrap_err() ); } @@ -765,7 +769,7 @@ }); let res = dialects.parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: FROM, found: WHERE".to_string()), + parser_error("Expected: FROM, found: WHERE".to_string()), res.unwrap_err() ); } @@ -1080,7 +1084,7 @@ let res = dialects.parse_sql_statements("SELECT 1 FROM T WHERE 1 = 2 (+)"); assert_eq!( - ParserError::ParserError("Expected: column identifier before (+), found: 2".to_string()), + parser_error("Expected: column identifier before (+), found: 2".to_string()), res.unwrap_err() ); } @@ -1113,7 +1117,7 @@ fn parse_select_distinct_missing_paren() { let result = parse_sql_statements("SELECT DISTINCT (name, id FROM customer"); assert_eq!( - ParserError::ParserError("Expected: ), found: FROM".to_string()), + parser_error("Expected: ), found: FROM".to_string()), result.unwrap_err(), ); } @@ -1127,17 +1131,17 @@ fn parse_select_all_distinct() { let result = parse_sql_statements("SELECT ALL DISTINCT name FROM customer"); assert_eq!( - ParserError::ParserError("Cannot specify ALL then DISTINCT".to_string()), + parser_error("Cannot specify ALL then DISTINCT".to_string()), result.unwrap_err(), ); let result = parse_sql_statements("SELECT DISTINCT ALL name FROM customer"); assert_eq!( - ParserError::ParserError("Cannot specify DISTINCT then ALL".to_string()), + parser_error("Cannot specify DISTINCT then ALL".to_string()), result.unwrap_err(), ); let result = parse_sql_statements("SELECT ALL DISTINCT ON(name) name FROM customer"); assert_eq!( - ParserError::ParserError("Cannot specify ALL then DISTINCT".to_string()), + parser_error("Cannot specify ALL then DISTINCT".to_string()), result.unwrap_err(), ); } @@ -1171,7 +1175,7 @@ let sql = "SELECT * INTO table0 asdf FROM table1"; let result = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: asdf".to_string()), + parser_error("Expected: end of statement, found: asdf".to_string()), result.unwrap_err() ) } @@ -1211,7 +1215,7 @@ let sql = "SELECT * + * FROM foo;"; let result = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: +".to_string()), + parser_error("Expected: end of statement, found: +".to_string()), result.unwrap_err(), ); } @@ -1319,7 +1323,7 @@ // Invalid let res = dialects.parse_sql_statements("SELECT foo.*.* FROM T"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: .".to_string()), + parser_error("Expected: end of statement, found: .".to_string()), res.unwrap_err() ); @@ -1366,13 +1370,13 @@ fn test_eof_after_as() { let res = parse_sql_statements("SELECT foo AS"); assert_eq!( - ParserError::ParserError("Expected: an identifier after AS, found: EOF".to_string()), + parser_error("Expected: an identifier after AS, found: EOF".to_string()), res.unwrap_err() ); let res = parse_sql_statements("SELECT 1 FROM foo AS"); assert_eq!( - ParserError::ParserError("Expected: an identifier after AS, found: EOF".to_string()), + parser_error("Expected: an identifier after AS, found: EOF".to_string()), res.unwrap_err() ); } @@ -1383,7 +1387,7 @@ let res = dialects.parse_sql_statements("ASSERT-URA<<"); assert_eq!( - ParserError::ParserError("No infix parser for token ShiftLeft".to_string()), + parser_error("No infix parser for token ShiftLeft".to_string()), res.unwrap_err() ); } @@ -1442,7 +1446,7 @@ let sql = "SELECT COUNT(ALL DISTINCT + x) FROM customer"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Cannot specify both ALL and DISTINCT".to_string()), + parser_error("Cannot specify both ALL and DISTINCT".to_string()), res.unwrap_err() ); } @@ -1458,7 +1462,7 @@ fn parse_invalid_infix_not() { let res = parse_sql_statements("SELECT c FROM t WHERE c NOT ("); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: NOT".to_string()), + parser_error("Expected: end of statement, found: NOT".to_string()), res.unwrap_err(), ); } @@ -2623,7 +2627,7 @@ let res = all_dialects_except(|d| d.supports_in_unparenthesized_expr()).parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: (, found: segment".to_string()), + parser_error("Expected: (, found: segment".to_string()), res.unwrap_err() ); } @@ -2835,14 +2839,14 @@ let sql = "select (1"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: ), found: EOF".to_string()), + parser_error("Expected: ), found: EOF".to_string()), res.unwrap_err() ); let sql = "select (), 2"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: an expression, found: )".to_string()), + parser_error("Expected: an expression, found: )".to_string()), res.unwrap_err() ); } @@ -3145,7 +3149,7 @@ let err = dialects .parse_sql_statements("SELECT a FROM t ORDER BY a USING OPERATOR();") .unwrap_err(); - assert!(matches!(err, ParserError::ParserError(_))); + assert!(matches!(err, ParserError::ParserError { .. })); } #[test] @@ -3615,7 +3619,7 @@ let dialects = all_dialects_except(|d| d.allow_extract_custom()); let res = dialects.parse_sql_statements("SELECT EXTRACT(JIFFY FROM d)"); assert_eq!( - ParserError::ParserError("Expected: date/time field, found: JIFFY".to_string()), + parser_error("Expected: date/time field, found: JIFFY".to_string()), res.unwrap_err() ); } @@ -3718,7 +3722,7 @@ let dialects = all_dialects_except(|d| d.allow_extract_custom()); let res = dialects.parse_sql_statements("SELECT CEIL(d TO JIFFY) FROM df"); assert_eq!( - ParserError::ParserError("Expected: date/time field, found: JIFFY".to_string()), + parser_error("Expected: date/time field, found: JIFFY".to_string()), res.unwrap_err() ); } @@ -3745,7 +3749,7 @@ let dialects = all_dialects_except(|d| d.allow_extract_custom()); let res = dialects.parse_sql_statements("SELECT FLOOR(d TO JIFFY) FROM df"); assert_eq!( - ParserError::ParserError("Expected: date/time field, found: JIFFY".to_string()), + parser_error("Expected: date/time field, found: JIFFY".to_string()), res.unwrap_err() ); } @@ -3951,7 +3955,7 @@ let sql = "SELECT LAG(1 IGNORE NULLS) IGNORE NULLS OVER () FROM t1"; assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: end of statement, found: NULLS".to_string()) + parser_error("Expected: end of statement, found: NULLS".to_string()) ); let sql = "SELECT LAG(1 IGNORE NULLS) IGNORE NULLS OVER () FROM t1"; @@ -3959,7 +3963,7 @@ all_dialects_where(|d| !d.supports_window_function_null_treatment_arg()) .parse_sql_statements(sql) .unwrap_err(), - ParserError::ParserError("Expected: ), found: IGNORE".to_string()) + parser_error("Expected: ), found: IGNORE".to_string()) ); } @@ -4684,7 +4688,7 @@ assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: >, found: )".to_string()) + parser_error("Expected: >, found: )".to_string()) ); } @@ -5317,14 +5321,12 @@ assert_eq!( parse_sql_statements("RENAME TABLE old_table TO new_table a").unwrap_err(), - ParserError::ParserError("Expected: end of statement, found: a".to_string()) + parser_error("Expected: end of statement, found: a".to_string()) ); assert_eq!( parse_sql_statements("RENAME TABLE1 old_table TO new_table a").unwrap_err(), - ParserError::ParserError( - "Expected: KEYWORD `TABLE` after RENAME, found: TABLE1".to_string() - ) + parser_error("Expected: KEYWORD `TABLE` after RENAME, found: TABLE1".to_string()) ); } @@ -5358,7 +5360,7 @@ .parse_sql_statements("ALTER TABLE t ON CLUSTER 123 ADD CONSTRAINT bar PRIMARY KEY (baz)"); assert_eq!( res.unwrap_err(), - ParserError::ParserError("Expected: identifier, found: 123".to_string()) + parser_error("Expected: identifier, found: 123".to_string()) ) } @@ -5630,7 +5632,7 @@ "{alter_stmt} ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'" )); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: USING".to_string()), + parser_error("Expected: end of statement, found: USING".to_string()), res.unwrap_err() ); } @@ -5661,7 +5663,7 @@ let res = parse_sql_statements("ALTER TABLE tab DROP CONSTRAINT is_active TEXT"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: TEXT".to_string()), + parser_error("Expected: end of statement, found: TEXT".to_string()), res.unwrap_err() ); } @@ -5670,15 +5672,13 @@ fn parse_bad_constraint() { let res = parse_sql_statements("ALTER TABLE tab ADD"); assert_eq!( - ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + parser_error("Expected: identifier, found: EOF".to_string()), res.unwrap_err() ); let res = parse_sql_statements("CREATE TABLE tab (foo int,"); assert_eq!( - ParserError::ParserError( - "Expected: column name or constraint definition, found: EOF".to_string() - ), + parser_error("Expected: column name or constraint definition, found: EOF".to_string()), res.unwrap_err() ); } @@ -5905,7 +5905,7 @@ // missing PLAN keyword should return error assert_eq!( - ParserError::ParserError("Expected: end of statement, found: SELECT".to_string()), + parser_error("Expected: end of statement, found: SELECT".to_string()), all_dialects() .parse_sql_statements("EXPLAIN QUERY SELECT sqrt(id) FROM foo") .unwrap_err() @@ -6178,7 +6178,7 @@ }); let res = dialects.parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: (, found: window2".to_string()), + parser_error("Expected: (, found: window2".to_string()), res.unwrap_err() ); } @@ -6687,13 +6687,13 @@ let result = parse_sql_statements("SELECT INTERVAL '1' SECOND TO SECOND"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: SECOND".to_string()), + parser_error("Expected: end of statement, found: SECOND".to_string()), result.unwrap_err(), ); let result = parse_sql_statements("SELECT INTERVAL '10' HOUR (1) TO HOUR (2)"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: (".to_string()), + parser_error("Expected: end of statement, found: (".to_string()), result.unwrap_err(), ); @@ -7349,13 +7349,13 @@ let res = parse_sql_statements("SELECT * FROM TABLE '1' AS a"); assert_eq!( - ParserError::ParserError("Expected: (, found: \'1\'".to_string()), + parser_error("Expected: (, found: \'1\'".to_string()), res.unwrap_err() ); let res = parse_sql_statements("SELECT * FROM TABLE (FUN(a) AS a"); assert_eq!( - ParserError::ParserError("Expected: ), found: AS".to_string()), + parser_error("Expected: ), found: AS".to_string()), res.unwrap_err() ); } @@ -8062,7 +8062,7 @@ let sql = "SELECT * FROM t1 natural"; assert_eq!( - ParserError::ParserError("Expected: a join type after NATURAL, found: EOF".to_string()), + parser_error("Expected: a join type after NATURAL, found: EOF".to_string()), parse_sql_statements(sql).unwrap_err(), ); } @@ -8138,7 +8138,7 @@ let dialects = all_dialects_except(|d| d.is_table_alias(&Keyword::OUTER, &mut Parser::new(d))); let res = dialects.parse_sql_statements("SELECT * FROM a OUTER JOIN b ON 1"); assert_eq!( - ParserError::ParserError("Expected: APPLY, found: JOIN".to_string()), + parser_error("Expected: APPLY, found: JOIN".to_string()), res.unwrap_err() ); } @@ -8383,7 +8383,7 @@ // Check that forgetting the semicolon results in an error: let res = parse_sql_statements(&(sql1.to_owned() + " " + sql2_kw + sql2_rest)); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: ".to_string() + sql2_kw), + parser_error("Expected: end of statement, found: ".to_string() + sql2_kw), res.unwrap_err() ); } @@ -8451,7 +8451,7 @@ "SELECT OVERLAY('abccccde' PLACING 'abc' FROM 3 FOR 12)", ); assert_eq!( - ParserError::ParserError("Expected: PLACING, found: FROM".to_owned()), + parser_error("Expected: PLACING, found: FROM".to_owned()), parse_sql_statements("SELECT OVERLAY('abccccde' FROM 3)").unwrap_err(), ); @@ -8502,7 +8502,7 @@ ); assert_eq!( - ParserError::ParserError("Expected: ), found: 'xyz'".to_owned()), + parser_error("Expected: ), found: 'xyz'".to_owned()), parse_sql_statements("SELECT TRIM(FOO 'xyz' FROM 'xyzfooxyz')").unwrap_err() ); @@ -8549,13 +8549,13 @@ // dialects without comma-style TRIM syntax should fail let unsupported_dialects = all_dialects_where(|d| !d.supports_comma_separated_trim()); assert_eq!( - ParserError::ParserError("Expected: ), found: ,".to_owned()), + parser_error("Expected: ), found: ,".to_owned()), unsupported_dialects .parse_sql_statements("SELECT TRIM('xyz', 'a')") .unwrap_err() ); assert_eq!( - ParserError::ParserError("Expected: ), found: 'xyz'".to_owned()), + parser_error("Expected: ), found: 'xyz'"), unsupported_dialects .parse_sql_statements("SELECT TRIM(FROM 'xyz')") .unwrap_err() @@ -8591,7 +8591,7 @@ let res = all_dialects_except(|d| d.is::<DatabricksDialect>()) .parse_sql_statements("SELECT EXISTS ("); assert_eq!( - ParserError::ParserError( + parser_error( "Expected: SELECT, VALUES, or a subquery in the query body, found: EOF".to_string() ), res.unwrap_err(), @@ -8600,7 +8600,7 @@ let res = all_dialects_except(|d| d.is::<DatabricksDialect>()) .parse_sql_statements("SELECT EXISTS (NULL)"); assert_eq!( - ParserError::ParserError( + parser_error( "Expected: SELECT, VALUES, or a subquery in the query body, found: NULL".to_string() ), res.unwrap_err(), @@ -9092,13 +9092,13 @@ let sql = "DROP TABLE"; assert_eq!( - ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + parser_error("Expected: identifier, found: EOF".to_string()), parse_sql_statements(sql).unwrap_err(), ); let sql = "DROP TABLE IF EXISTS foo, bar CASCADE RESTRICT"; assert_eq!( - ParserError::ParserError("Cannot specify both CASCADE and RESTRICT in DROP".to_string()), + parser_error("Cannot specify both CASCADE and RESTRICT in DROP".to_string()), parse_sql_statements(sql).unwrap_err(), ); } @@ -9158,7 +9158,7 @@ fn parse_invalid_subquery_without_parens() { let res = parse_sql_statements("SELECT SELECT 1 FROM bar WHERE 1=1 FROM baz"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: 1".to_string()), + parser_error("Expected: end of statement, found: 1".to_string()), res.unwrap_err() ); } @@ -9389,14 +9389,14 @@ let sql = "SELECT * FROM LATERAL UNNEST ([10,20,30]) as numbers WITH OFFSET;"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: WITH".to_string()), + parser_error("Expected: end of statement, found: WITH".to_string()), res.unwrap_err() ); let sql = "SELECT * FROM a LEFT JOIN LATERAL (b CROSS JOIN c)"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError( + parser_error( "Expected: SELECT, VALUES, or a subquery in the query body, found: b".to_string() ), res.unwrap_err() @@ -9522,19 +9522,19 @@ let res = dialects.parse_sql_statements("START TRANSACTION ISOLATION LEVEL BAD"); assert_eq!( - ParserError::ParserError("Expected: isolation level, found: BAD".to_string()), + parser_error("Expected: isolation level, found: BAD".to_string()), res.unwrap_err() ); let res = dialects.parse_sql_statements("START TRANSACTION BAD"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: BAD".to_string()), + parser_error("Expected: end of statement, found: BAD".to_string()), res.unwrap_err() ); let res = dialects.parse_sql_statements("START TRANSACTION READ ONLY,"); assert_eq!( - ParserError::ParserError("Expected: transaction mode, found: EOF".to_string()), + parser_error("Expected: transaction mode, found: EOF".to_string()), res.unwrap_err() ); @@ -9688,7 +9688,7 @@ ]; for (sql, error) in error_sqls { assert_eq!( - ParserError::ParserError(error.to_string()), + parser_error(error.to_string()), multi_variable_dialects .parse_sql_statements(sql) .unwrap_err() @@ -9870,7 +9870,7 @@ assert_eq!( parse_sql_statements("ABORT TO test1").unwrap_err(), - ParserError::ParserError("Expected: end of statement, found: TO".to_string()), + parser_error("Expected: end of statement, found: TO".to_string()), ); } @@ -10807,10 +10807,7 @@ ), ] { let res = dialects.parse_sql_statements(sql); - assert_eq!( - ParserError::ParserError(err_msg.to_string()), - res.unwrap_err() - ); + assert_eq!(parser_error(err_msg.to_string()), res.unwrap_err()); } } @@ -11091,19 +11088,19 @@ // Can't repeat OFFSET / LIMIT let res = parse_sql_statements("SELECT foo FROM bar OFFSET 2 OFFSET 2"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: OFFSET".to_string()), + parser_error("Expected: end of statement, found: OFFSET".to_string()), res.unwrap_err() ); let res = parse_sql_statements("SELECT foo FROM bar LIMIT 2 LIMIT 2"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: LIMIT".to_string()), + parser_error("Expected: end of statement, found: LIMIT".to_string()), res.unwrap_err() ); let res = parse_sql_statements("SELECT foo FROM bar OFFSET 2 LIMIT 2 OFFSET 2"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: OFFSET".to_string()), + parser_error("Expected: end of statement, found: OFFSET".to_string()), res.unwrap_err() ); } @@ -11183,7 +11180,7 @@ let res = all_dialects_except(|d| d.supports_in_unparenthesized_expr()).parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: (, found: )".to_string()), + parser_error("Expected: (, found: )".to_string()), res.unwrap_err() ); @@ -11191,7 +11188,7 @@ all_dialects_where(|d| d.supports_in_unparenthesized_expr()).parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: an expression, found: )".to_string()), + parser_error("Expected: an expression, found: )".to_string()), result_unparenthesized.unwrap_err() ); } @@ -11299,7 +11296,7 @@ ] { assert_eq!( parse_sql_statements(sql), - Err(ParserError::ParserError(format!( + Err(parser_error(format!( "Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [NOT] JSON [VALUE | SCALAR | ARRAY | OBJECT] [WITH | WITHOUT UNIQUE [KEYS]] | [form] NORMALIZED FROM after IS, found: {found}" ))), "{sql}" @@ -11405,7 +11402,7 @@ ] { assert_eq!( dialects.parse_sql_statements(sql), - Err(ParserError::ParserError(err.to_string())), + Err(parser_error(err.to_string())), "{sql}" ); } @@ -11596,7 +11593,7 @@ let res = parse_sql_statements("CACHE TABLE 'table_name' foo"); assert_eq!( - ParserError::ParserError( + parser_error( "Expected: SELECT, VALUES, or a subquery in the query body, found: foo".to_string() ), res.unwrap_err() @@ -11604,7 +11601,7 @@ let res = parse_sql_statements("CACHE flag TABLE 'table_name' OPTIONS('K1'='V1') foo"); assert_eq!( - ParserError::ParserError( + parser_error( "Expected: SELECT, VALUES, or a subquery in the query body, found: foo".to_string() ), res.unwrap_err() @@ -11612,7 +11609,7 @@ let res = parse_sql_statements("CACHE TABLE 'table_name' AS foo"); assert_eq!( - ParserError::ParserError( + parser_error( "Expected: SELECT, VALUES, or a subquery in the query body, found: foo".to_string() ), res.unwrap_err() @@ -11620,7 +11617,7 @@ let res = parse_sql_statements("CACHE flag TABLE 'table_name' OPTIONS('K1'='V1') AS foo"); assert_eq!( - ParserError::ParserError( + parser_error( "Expected: SELECT, VALUES, or a subquery in the query body, found: foo".to_string() ), res.unwrap_err() @@ -11628,19 +11625,19 @@ let res = parse_sql_statements("CACHE 'table_name'"); assert_eq!( - ParserError::ParserError("Expected: a `TABLE` keyword, found: 'table_name'".to_string()), + parser_error("Expected: a `TABLE` keyword, found: 'table_name'".to_string()), res.unwrap_err() ); let res = parse_sql_statements("CACHE 'table_name' OPTIONS('K1'='V1')"); assert_eq!( - ParserError::ParserError("Expected: a `TABLE` keyword, found: OPTIONS".to_string()), + parser_error("Expected: a `TABLE` keyword, found: OPTIONS".to_string()), res.unwrap_err() ); let res = parse_sql_statements("CACHE flag 'table_name' OPTIONS('K1'='V1')"); assert_eq!( - ParserError::ParserError("Expected: a `TABLE` keyword, found: 'table_name'".to_string()), + parser_error("Expected: a `TABLE` keyword, found: 'table_name'".to_string()), res.unwrap_err() ); } @@ -11665,19 +11662,19 @@ let res = parse_sql_statements("UNCACHE TABLE 'table_name' foo"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: foo".to_string()), + parser_error("Expected: end of statement, found: foo".to_string()), res.unwrap_err() ); let res = parse_sql_statements("UNCACHE 'table_name' foo"); assert_eq!( - ParserError::ParserError("Expected: TABLE, found: 'table_name'".to_string()), + parser_error("Expected: TABLE, found: 'table_name'".to_string()), res.unwrap_err() ); let res = parse_sql_statements("UNCACHE IF EXISTS 'table_name' foo"); assert_eq!( - ParserError::ParserError("Expected: TABLE, found: IF".to_string()), + parser_error("Expected: TABLE, found: IF".to_string()), res.unwrap_err() ); } @@ -11686,21 +11683,30 @@ fn parse_deeply_nested_parens_hits_recursion_limits() { let sql = "(".repeat(1000); let res = parse_sql_statements(&sql); - assert_eq!(ParserError::RecursionLimitExceeded, res.unwrap_err()); + assert!(matches!( + res, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] fn parse_update_deeply_nested_parens_hits_recursion_limits() { let sql = format!("\nUPDATE\n\n\n\n\n\n\n\n\n\n{}", "(".repeat(1000)); let res = parse_sql_statements(&sql); - assert_eq!(ParserError::RecursionLimitExceeded, res.unwrap_err()); + assert!(matches!( + res, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] fn parse_deeply_nested_unary_op_hits_recursion_limits() { let sql = format!("SELECT {}", "+".repeat(1000)); let res = parse_sql_statements(&sql); - assert_eq!(ParserError::RecursionLimitExceeded, res.unwrap_err()); + assert!(matches!( + res, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -11715,7 +11721,10 @@ .expect("tokenize to work") .parse_statements(); - assert_eq!(res, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + res, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -11730,7 +11739,10 @@ .expect("tokenize to work") .parse_statements(); - assert_eq!(res, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + res, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -11744,7 +11756,10 @@ .expect("tokenize to work") .parse_statements(); - assert_eq!(res, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + res, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -11762,7 +11777,10 @@ .expect("tokenize to work") .parse_statements(); - assert_eq!(res, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + res, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -11787,7 +11805,10 @@ .with_recursion_limit(20) .parse_statements(); - assert_eq!(res, Err(ParserError::RecursionLimitExceeded)); + assert!(matches!( + res, + Err(ParserError::RecursionLimitExceeded { .. }) + )); // limit recursion to 50, expect it to succeed let res = Parser::new(&dialect) @@ -12454,7 +12475,7 @@ Box::new(MsSqlDialect {}), ]); assert_eq!( - ParserError::ParserError("Expected: an expression, found: 💝".to_string()), + parser_error("Expected: an expression, found: 💝".to_string()), supported_dialects .parse_sql_statements("SELECT 💝 FROM table1") .unwrap_err() @@ -12510,23 +12531,21 @@ trailing_commas .parse_sql_statements("SELECT name, age, from employees;") .unwrap_err(), - ParserError::ParserError("Expected an expression, found: from".to_string()) + parser_error("Expected an expression, found: from".to_string()) ); assert_eq!( trailing_commas .parse_sql_statements("REVOKE USAGE, SELECT, ON p TO u") .unwrap_err(), - ParserError::ParserError("Expected: a privilege keyword, found: ON".to_string()) + parser_error("Expected: a privilege keyword, found: ON".to_string()) ); assert_eq!( trailing_commas .parse_sql_statements("CREATE TABLE employees (name text, age int,)") .unwrap_err(), - ParserError::ParserError( - "Expected: column name or constraint definition, found: )".to_string() - ) + parser_error("Expected: column name or constraint definition, found: )".to_string()) ); let unsupported_dialects = all_dialects_where(|d| !d.supports_trailing_commas()); @@ -12534,7 +12553,7 @@ unsupported_dialects .parse_sql_statements("SELECT * FROM track ORDER BY milliseconds,") .unwrap_err(), - ParserError::ParserError("Expected: an expression, found: EOF".to_string()) + parser_error("Expected: an expression, found: EOF".to_string()) ); } @@ -12560,7 +12579,7 @@ unsupported_dialects .parse_sql_statements("SELECT album_id, name, FROM track") .unwrap_err(), - ParserError::ParserError("Expected an expression, found: FROM".to_string()) + parser_error("Expected an expression, found: FROM".to_string()) ); } @@ -12975,7 +12994,7 @@ dialects.verified_stmt("EXECUTE 'SELECT 1'"); assert_eq!( - ParserError::ParserError("Expected: identifier, found: ,".to_string()), + parser_error("Expected: identifier, found: ,".to_string()), dialects .parse_sql_statements("EXECUTE IMMEDIATE 'SELECT 1' USING 1 AS, y") .unwrap_err() @@ -13269,9 +13288,10 @@ all_dialects_where(|d| !d.supports_hash_comment()) .parse_sql_statements("SELECT 1 # it's a comment") .unwrap_err(), - ParserError::TokenizerError( - "Unterminated string literal at Line: 1, Column: 14".to_string() - ) + ParserError::TokenizerError { + message: "Unterminated string literal".to_string(), + span: Span::new(Location::new(1, 14), Location::new(1, 14)), + } ); } @@ -13298,7 +13318,7 @@ ] { assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("No infix parser for token Sharp".to_string()), + parser_error("No infix parser for token Sharp".to_string()), "{sql}" ); } @@ -14103,7 +14123,7 @@ ); assert_eq!( all_dialects_where(|d| d.supports_match_recognize()).parse_sql_statements(&sql), - Err(ParserError::ParserError(format!( + Err(parser_error(format!( "Expected: identifier, found: {found}" ))), "{pattern}" @@ -14796,7 +14816,7 @@ all_dialects().verified_stmt("TRUNCATE TABLE t"); assert_eq!( - ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + parser_error("Expected: identifier, found: EOF".to_string()), all_dialects() .parse_sql_statements("TRUNCATE TABLE t ON CLUSTER") .unwrap_err() @@ -15033,7 +15053,7 @@ .parse_sql_statements("CREATE POLICY my_policy ON my_table TO") .unwrap_err() .to_string(), - "sql parser error: Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. sql parser error: Expected: identifier, found: EOF" + "sql parser error: Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO, found: EOF" ); // missing USING expression assert_eq!( @@ -15387,14 +15407,14 @@ dialects .parse_sql_statements("SELECT a = FROM") .unwrap_err(), - ParserError::ParserError("Expected an expression, found: FROM".to_string()) + parser_error("Expected an expression, found: FROM") ); assert_eq!( dialects .parse_sql_statements("SELECT a = FROM some_table") .unwrap_err(), - ParserError::ParserError("Expected an expression, found: FROM".to_string()) + parser_error("Expected an expression, found: FROM") ); let dialects = all_dialects_where(|d| !d.supports_eq_alias_assignment()); @@ -15551,14 +15571,14 @@ assert_eq!( dialects.parse_sql_statements("LISTEN *").unwrap_err(), - ParserError::ParserError("Expected: identifier, found: *".to_string()) + parser_error("Expected: identifier, found: *".to_string()) ); let dialects = all_dialects_where(|d| !d.supports_listen_notify()); assert_eq!( dialects.parse_sql_statements("LISTEN test1").unwrap_err(), - ParserError::ParserError("Expected: an SQL statement, found: LISTEN".to_string()) + parser_error("Expected: an SQL statement, found: LISTEN".to_string()) ); } @@ -15582,14 +15602,14 @@ assert_eq!( dialects.parse_sql_statements("UNLISTEN +").unwrap_err(), - ParserError::ParserError("Expected: wildcard or identifier, found: +".to_string()) + parser_error("Expected: wildcard or identifier, found: +".to_string()) ); let dialects = all_dialects_where(|d| !d.supports_listen_notify()); assert_eq!( dialects.parse_sql_statements("UNLISTEN test1").unwrap_err(), - ParserError::ParserError("Expected: an SQL statement, found: UNLISTEN".to_string()) + parser_error("Expected: an SQL statement, found: UNLISTEN".to_string()) ); } @@ -15618,13 +15638,13 @@ assert_eq!( dialects.parse_sql_statements("NOTIFY *").unwrap_err(), - ParserError::ParserError("Expected: identifier, found: *".to_string()) + parser_error("Expected: identifier, found: *".to_string()) ); assert_eq!( dialects .parse_sql_statements("NOTIFY test1, *") .unwrap_err(), - ParserError::ParserError("Expected: literal string, found: *".to_string()) + parser_error("Expected: literal string, found: *".to_string()) ); let sql_statements = [ @@ -15636,7 +15656,7 @@ for &sql in &sql_statements { assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: an SQL statement, found: NOTIFY".to_string()) + parser_error("Expected: an SQL statement, found: NOTIFY".to_string()) ); } } @@ -15697,13 +15717,13 @@ only_supports_load_extension_dialects .parse_sql_statements(sql) .unwrap_err(), - ParserError::ParserError("Expected: end of statement, found: INPATH".to_string()) + parser_error("Expected: end of statement, found: INPATH".to_string()) ); assert_eq!( not_supports_load_dialects .parse_sql_statements(sql) .unwrap_err(), - ParserError::ParserError( + parser_error( "Expected: `DATA` or an extension name after `LOAD`, found: INPATH".to_string() ) ); @@ -15736,13 +15756,13 @@ only_supports_load_extension_dialects .parse_sql_statements(sql) .unwrap_err(), - ParserError::ParserError("Expected: end of statement, found: LOCAL".to_string()) + parser_error("Expected: end of statement, found: LOCAL".to_string()) ); assert_eq!( not_supports_load_dialects .parse_sql_statements(sql) .unwrap_err(), - ParserError::ParserError( + parser_error( "Expected: `DATA` or an extension name after `LOAD`, found: LOCAL".to_string() ) ); @@ -15848,7 +15868,7 @@ let sql = "LOAD DATA2 LOCAL INPATH '/local/path/to/data.txt' INTO TABLE test.my_table"; assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError( + parser_error( "Expected: `DATA` or an extension name after `LOAD`, found: DATA2".to_string() ) ); @@ -15871,7 +15891,7 @@ not_supports_load_extension_dialects .parse_sql_statements(sql) .unwrap_err(), - ParserError::ParserError( + parser_error( "Expected: `DATA` or an extension name after `LOAD`, found: my_extension".to_string() ) ); @@ -15936,7 +15956,7 @@ for &sql in &sql_statements { assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("No infix parser for token ExclamationMark".to_string()) + parser_error("No infix parser for token ExclamationMark".to_string()) ); } @@ -15946,7 +15966,7 @@ for &sql in &sql_statements { assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: an expression, found: !".to_string()) + parser_error("Expected: an expression, found: !".to_string()) ); } } @@ -15982,7 +16002,7 @@ for &sql in &sql_statements { assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: an expression, found: !".to_string()) + parser_error("Expected: an expression, found: !".to_string()) ); } @@ -15997,7 +16017,7 @@ for &sql in &sql_statements { assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("No infix parser for token ExclamationMark".to_string()) + parser_error("No infix parser for token ExclamationMark".to_string()) ); } @@ -16010,7 +16030,7 @@ for &sql in &sql_statements { assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("No infix parser for token ExclamationMark".to_string()) + parser_error("No infix parser for token ExclamationMark".to_string()) ); } } @@ -16093,7 +16113,7 @@ all_dialects_where(|d| d.supports_comment_on()) .parse_sql_statements("COMMENT ON TABLE t0") .unwrap_err(), - ParserError::ParserError("Expected: IS, found: EOF".to_string()) + parser_error("Expected: IS, found: EOF".to_string()) ); // missing comment literal @@ -16101,7 +16121,7 @@ all_dialects_where(|d| d.supports_comment_on()) .parse_sql_statements("COMMENT ON TABLE t0 IS") .unwrap_err(), - ParserError::ParserError("Expected: literal string, found: EOF".to_string()) + parser_error("Expected: literal string, found: EOF".to_string()) ); // unknown object type @@ -16109,7 +16129,7 @@ all_dialects_where(|d| d.supports_comment_on()) .parse_sql_statements("COMMENT ON UNKNOWN t0 IS 'comment'") .unwrap_err(), - ParserError::ParserError("Expected: comment object_type, found: UNKNOWN".to_string()) + parser_error("Expected: comment object_type, found: UNKNOWN".to_string()) ); } @@ -16129,7 +16149,7 @@ for sql in [sql_1, sql_2] { assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: end of statement, found: SELECT".to_string()) + parser_error("Expected: end of statement, found: SELECT".to_string()) ); } } @@ -16143,7 +16163,7 @@ let sql = "SELECT MAX(interval) FROM tbl"; assert_eq!( dialects.parse_sql_statements(sql), - Err(ParserError::ParserError( + Err(parser_error( "Expected: an expression, found: )".to_string() )) ); @@ -16415,7 +16435,7 @@ all_dialects() .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = ))") .unwrap_err(), - ParserError::ParserError("Expected: a value, found: )".to_string()) + parser_error("Expected: a value, found: )".to_string()) ); // invalid case that name is not a string @@ -16423,7 +16443,7 @@ all_dialects() .parse_sql_statements("CREATE TABLE t0 (foo ENUM8('a' = 1, 2))") .unwrap_err(), - ParserError::ParserError("Expected: literal string, found: 2".to_string()) + parser_error("Expected: literal string, found: 2".to_string()) ); } @@ -16530,7 +16550,7 @@ let query = "UPDATE t1 FROM (SELECT name, id FROM t1 GROUP BY id) AS t2 SET name = t2.name FROM (SELECT name from t2) AS t2"; assert_eq!( - ParserError::ParserError("Expected: end of statement, found: FROM".to_string()), + parser_error("Expected: end of statement, found: FROM".to_string()), parse_sql_statements(query).unwrap_err() ); } @@ -16560,9 +16580,7 @@ unsupported_dialects .parse_sql_statements("CREATE TABLE employees (name text, age int,)") .unwrap_err(), - ParserError::ParserError( - "Expected: column name or constraint definition, found: )".to_string() - ), + parser_error("Expected: column name or constraint definition, found: )".to_string()), ); } @@ -16669,11 +16687,11 @@ )); assert_eq!( - ParserError::ParserError("Expected: THEN, found: END".to_string()), + parser_error("Expected: THEN, found: END".to_string()), parse_sql_statements("CASE 1 WHEN a END").unwrap_err() ); assert_eq!( - ParserError::ParserError("Expected: WHEN, found: ELSE".to_string()), + parser_error("Expected: WHEN, found: ELSE".to_string()), parse_sql_statements("CASE 1 ELSE SELECT 1; END").unwrap_err() ); } @@ -16752,7 +16770,7 @@ dialects.verified_stmt("IF 1 THEN SELECT 1; ELSEIF 1 THEN END IF"); assert_eq!( - ParserError::ParserError("Expected: IF, found: EOF".to_string()), + parser_error("Expected: IF, found: EOF".to_string()), dialects .parse_sql_statements("IF 1 THEN SELECT 1; ELSEIF 1 THEN SELECT 2; END") .unwrap_err() @@ -16832,7 +16850,7 @@ verified_stmt("RAISE"); assert_eq!( - ParserError::ParserError("Expected: =, found: error".to_string()), + parser_error("Expected: =, found: error".to_string()), parse_sql_statements("RAISE USING MESSAGE error").unwrap_err() ); } @@ -17709,7 +17727,7 @@ // Test that plain EXCEPT without DISTINCT fails assert_eq!( - ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()), + parser_error("EXCEPT pipe operator requires DISTINCT modifier".to_string()), dialects .parse_sql_statements("SELECT * FROM users |> EXCEPT (SELECT * FROM admins)") .unwrap_err() @@ -17717,7 +17735,7 @@ // Test that EXCEPT ALL fails assert_eq!( - ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()), + parser_error("EXCEPT pipe operator requires DISTINCT modifier".to_string()), dialects .parse_sql_statements("SELECT * FROM users |> EXCEPT ALL (SELECT * FROM admins)") .unwrap_err() @@ -17725,7 +17743,7 @@ // Test that EXCEPT BY NAME without DISTINCT fails assert_eq!( - ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()), + parser_error("EXCEPT pipe operator requires DISTINCT modifier".to_string()), dialects .parse_sql_statements("SELECT * FROM users |> EXCEPT BY NAME (SELECT * FROM admins)") .unwrap_err() @@ -17733,7 +17751,7 @@ // Test that EXCEPT ALL BY NAME fails assert_eq!( - ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()), + parser_error("EXCEPT pipe operator requires DISTINCT modifier".to_string()), dialects .parse_sql_statements( "SELECT * FROM users |> EXCEPT ALL BY NAME (SELECT * FROM admins)" @@ -17743,7 +17761,7 @@ // Test that plain INTERSECT without DISTINCT fails assert_eq!( - ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()), + parser_error("INTERSECT pipe operator requires DISTINCT modifier".to_string()), dialects .parse_sql_statements("SELECT * FROM users |> INTERSECT (SELECT * FROM admins)") .unwrap_err() @@ -17751,7 +17769,7 @@ // Test that INTERSECT ALL fails assert_eq!( - ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()), + parser_error("INTERSECT pipe operator requires DISTINCT modifier".to_string()), dialects .parse_sql_statements("SELECT * FROM users |> INTERSECT ALL (SELECT * FROM admins)") .unwrap_err() @@ -17759,7 +17777,7 @@ // Test that INTERSECT BY NAME without DISTINCT fails assert_eq!( - ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()), + parser_error("INTERSECT pipe operator requires DISTINCT modifier".to_string()), dialects .parse_sql_statements("SELECT * FROM users |> INTERSECT BY NAME (SELECT * FROM admins)") .unwrap_err() @@ -17767,7 +17785,7 @@ // Test that INTERSECT ALL BY NAME fails assert_eq!( - ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()), + parser_error("INTERSECT pipe operator requires DISTINCT modifier".to_string()), dialects .parse_sql_statements( "SELECT * FROM users |> INTERSECT ALL BY NAME (SELECT * FROM admins)" @@ -17899,10 +17917,7 @@ ), ] { let res = dialects.parse_sql_statements(sql); - assert_eq!( - ParserError::ParserError(err_msg.to_string()), - res.unwrap_err() - ); + assert_eq!(parser_error(err_msg.to_string()), res.unwrap_err()); } } @@ -18677,7 +18692,7 @@ .parse_sql_statements("SELECT *, c1 EXCLUDE c2 FROM test") .err() .unwrap(), - ParserError::ParserError("Expected: end of statement, found: c2".to_string()) + parser_error("Expected: end of statement, found: c2".to_string()) ); // Dialects that only support the wildcard form and accept EXCLUDE as an implicity alias @@ -18692,7 +18707,7 @@ .parse_sql_statements("SELECT *, c1 EXCLUDE c2 FROM test") .err() .unwrap(), - ParserError::ParserError("Expected: end of statement, found: EXCLUDE".to_string()) + parser_error("Expected: end of statement, found: EXCLUDE".to_string()) ); } @@ -18803,11 +18818,11 @@ let res = supports_dictionary.parse_sql_statements(sql); let res_dict = dictionary_unsupported.parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: :, found: '14:12:01'".to_string()), + parser_error("Expected: :, found: '14:12:01'".to_string()), res.unwrap_err() ); assert_eq!( - ParserError::ParserError("Expected: an expression, found: {".to_string()), + parser_error("Expected: an expression, found: {".to_string()), res_dict.unwrap_err() ); } @@ -18944,7 +18959,7 @@ let sql = "CREATE VIEW IF NOT EXISTS AS SELECT 1"; let res = all_dialects().parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected: AS, found: SELECT".to_string()), + parser_error("Expected: AS, found: SELECT".to_string()), res.unwrap_err() ); } @@ -19271,7 +19286,7 @@ ] { assert_eq!( dialects.parse_sql_statements(sql), - Err(ParserError::ParserError(err.to_string())), + Err(parser_error(err.to_string())), "{sql}" ); } @@ -19766,7 +19781,7 @@ // This should return a parser error, not panic. assert_eq!( parse_sql_statements("SET AUTHORIZATION TIME TIME"), - Err(ParserError::ParserError( + Err(parser_error( "Expected: SESSION, LOCAL, or other scope modifier before AUTHORIZATION, found: AUTHORIZATION" .to_string() )) @@ -19984,7 +19999,7 @@ .verified_stmt("SELECT stack(2, 'a', 'b', 'c', 'd') AS (col1, col2) FROM t"); assert_eq!( - ParserError::ParserError("Expected: an identifier after AS, found: (".to_string()), + parser_error("Expected: an identifier after AS, found: (".to_string()), all_dialects_where(|d| !d.supports_select_item_multi_column_alias()) .parse_sql_statements("SELECT stack(2, 'a', 'b') AS (col1, col2)") .unwrap_err() @@ -20009,7 +20024,7 @@ dialects.verified_only_select(r#"SELECT foo(a AS "x y")"#); dialects.verified_only_select("SELECT foo(bar(a AS x) AS y)"); assert_eq!( - ParserError::ParserError("Expected: ), found: AS".to_string()), + parser_error("Expected: ), found: AS".to_string()), all_dialects_except(|d| d.supports_aliased_function_args()) .parse_sql_statements("SELECT foo(a AS x)") .unwrap_err() @@ -20049,7 +20064,7 @@ // XMLPARSE needs both a mode word and a value. assert_eq!( - ParserError::ParserError("Expected: an expression, found: )".to_string()), + parser_error("Expected: an expression, found: )".to_string()), dialects .parse_sql_statements("SELECT xmlparse('<a/>')") .unwrap_err() @@ -20065,7 +20080,7 @@ let others = all_dialects_except(|d| d.supports_xml_expressions()); others.verified_only_select("SELECT xmlparse(1)"); assert_eq!( - ParserError::ParserError("Expected: ), found: '<a/>'".to_string()), + parser_error("Expected: ), found: '<a/>'".to_string()), others .parse_sql_statements("SELECT xmlparse(content '<a/>')") .unwrap_err() @@ -20338,7 +20353,7 @@ .parse_sql_statements("CREATE TABLE new_table AS TABLE %mpty") .unwrap_err(); assert_eq!( - ParserError::ParserError("Expected: identifier, found: %".to_string()), + parser_error("Expected: identifier, found: %".to_string()), err ); @@ -20346,7 +20361,7 @@ .parse_sql_statements("CREATE TABLE new_table AS TABLE `x` ORE") .unwrap_err(); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: ORE".to_string()), + parser_error("Expected: end of statement, found: ORE".to_string()), err ); } @@ -20369,7 +20384,7 @@ unsupported .parse_sql_statements("SELECT * FROM @stage") .unwrap_err(), - ParserError::ParserError("Expected: identifier, found: @".to_string()), + parser_error("Expected: identifier, found: @".to_string()), ); } @@ -20401,7 +20416,7 @@ _ => unreachable!(), } assert_eq!( - ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + parser_error("Expected: identifier, found: EOF".to_string()), dialects .parse_sql_statements("ALTER TABLE tab ADD COLUMN c INT AFTER") .unwrap_err() @@ -20414,7 +20429,7 @@ ] { assert_eq!( dialects.parse_sql_statements(sql), - Err(ParserError::ParserError(format!( + Err(parser_error(format!( "Expected: end of statement, found: {found}" ))), "{sql}" @@ -20435,10 +20450,7 @@ let err = all_dialects_where(|d| !d.supports_numeric_prefix()) .parse_sql_statements("SELECT (1.i)") .unwrap_err(); - assert_eq!( - err, - ParserError::ParserError("Expected: ), found: i".to_string()) - ); + assert_eq!(err, parser_error("Expected: ), found: i")); } #[test] @@ -20454,33 +20466,18 @@ // Quoted identifiers are not valid placeholders let err = dialects.parse_sql_statements("SELECT :`a`").unwrap_err(); - assert_eq!( - ParserError::ParserError("Expected: placeholder, found: `a`".to_string()), - err - ); + assert_eq!(parser_error("Expected: placeholder, found: `a`"), err); let err = dialects.parse_sql_statements("SELECT :\"a\"").unwrap_err(); - assert_eq!( - ParserError::ParserError("Expected: placeholder, found: \"a\"".to_string()), - err - ); + assert_eq!(parser_error("Expected: placeholder, found: \"a\""), err); let err = dialects.parse_sql_statements("SELECT:` a` a").unwrap_err(); - assert_eq!( - ParserError::ParserError("Expected: placeholder, found: ` a`".to_string()), - err - ); + assert_eq!(parser_error("Expected: placeholder, found: ` a`"), err); let ansi = TestedDialects::new(vec![Box::new(AnsiDialect {})]); ansi.verified_stmt("SELECT @x"); let err = ansi.parse_sql_statements("SELECT @`a`").unwrap_err(); - assert_eq!( - ParserError::ParserError("Expected: placeholder, found: `a`".to_string()), - err - ); + assert_eq!(parser_error("Expected: placeholder, found: `a`"), err); let err = ansi.parse_sql_statements("SELECT @\"a\"").unwrap_err(); - assert_eq!( - ParserError::ParserError("Expected: placeholder, found: \"a\"".to_string()), - err - ); + assert_eq!(parser_error("Expected: placeholder, found: \"a\""), err); } #[test] @@ -20548,21 +20545,21 @@ ); assert_eq!( - ParserError::ParserError("Expected: ), found: 1".to_string()), + parser_error("Expected: ), found: 1"), parse_sql_statements("SELECT * FROM t WHERE x IN ((SELECT a FROM u) 1)").unwrap_err() ); assert_eq!( - ParserError::ParserError("Expected: ), found: EOF".to_string()), + parser_error("Expected: ), found: EOF"), parse_sql_statements("SELECT * FROM t WHERE x IN (SELECT a FROM u").unwrap_err() ); assert_eq!( - ParserError::ParserError("Expected: ), found: EOF".to_string()), + parser_error("Expected: ), found: EOF"), parse_sql_statements("SELECT * FROM t WHERE x IN ((SELECT a FROM u), 1").unwrap_err() ); // The inner `IN` is read again for the outer list after its subquery // arm was cached as failed. assert_eq!( - ParserError::ParserError("Expected: ), found: EOF".to_string()), + parser_error("Expected: ), found: EOF"), parse_sql_statements("SELECT * FROM t WHERE x IN ((SELECT 1 FROM u WHERE y IN (SELECT 2") .unwrap_err() ); @@ -20704,15 +20701,11 @@ dialects.verified_stmt("SELECT CAST(NULL AS ARRAY<STRUCT<a INT>>[])"); assert_eq!( dialects.parse_sql_statements("SELECT CAST(NULL AS STRUCT<a STRUCT<b INT>>[)"), - Err(ParserError::ParserError( - "Expected: ], found: )".to_string() - )) + Err(parser_error("Expected: ], found: )".to_string())) ); assert_eq!( dialects.parse_sql_statements("SELECT CAST(NULL AS STRUCT<a STRUCT<b INT>>[x])"), - Err(ParserError::ParserError( - "Expected: ], found: x".to_string() - )) + Err(parser_error("Expected: ], found: x".to_string())) ); let dialects = all_dialects_where(|d| d.supports_struct_literal()); @@ -20755,3 +20748,112 @@ dialects.verified_stmt("SELECT 1 AS 'x'"); dialects.verified_stmt("SELECT a FROM t AS 'y'"); } + +#[test] +fn parse_error_at_end_of_input_reports_location() { + let dialects = all_dialects(); + assert_eq!( + dialects + .parse_sql_statements_with_locations("SELECT 1 FROM t WHERE") + .unwrap_err() + .to_string(), + "sql parser error: Expected: an expression, found: EOF at Line: 1, Column: 22" + ); + assert_eq!( + dialects + .parse_sql_statements_with_locations("SELECT 1\nFROM t\nWHERE\n-- trailing\n") + .unwrap_err() + .to_string(), + "sql parser error: Expected: an expression, found: EOF at Line: 3, Column: 6" + ); + for sql in ["", " \n "] { + let err = dialects.one_of_identical_results(|dialect| { + Parser::new(dialect) + .try_with_sql(sql) + .unwrap() + .parse_expr() + .unwrap_err() + }); + assert_eq!( + err.to_string(), + "sql parser error: Expected: an expression, found: EOF at Line: 1, Column: 1" + ); + } +} + +#[test] +fn parse_error_span() { + let dialect = GenericDialect {}; + let err = Parser::parse_sql(&dialect, "SELECT 1 FROM t x y").unwrap_err(); + assert_eq!( + err.to_string(), + "sql parser error: Expected: end of statement, found: y at Line: 1, Column: 19" + ); + assert_eq!( + err.span(), + Span::new(Location::new(1, 19), Location::new(1, 20)) + ); + + let err = Parser::parse_sql(&dialect, "SELECT 1 FROM t WHERE").unwrap_err(); + assert_eq!( + err.span(), + Span::new(Location::new(1, 22), Location::new(1, 22)) + ); + + let err = Parser::parse_sql(&dialect, "SELECT 'abc").unwrap_err(); + assert_eq!( + err.to_string(), + "sql parser error: Unterminated string literal at Line: 1, Column: 8" + ); + assert_eq!( + err.span(), + Span::new(Location::new(1, 8), Location::new(1, 8)) + ); +} + +#[test] +fn parse_recursion_limit_error_reports_location() { + let err = Parser::new(&GenericDialect {}) + .with_recursion_limit(4) + .try_with_sql("SELECT (((1)))") + .unwrap() + .parse_statements() + .unwrap_err(); + assert_eq!( + err.to_string(), + "sql parser error: recursion limit exceeded at Line: 1, Column: 9" + ); + assert_eq!( + err, + ParserError::RecursionLimitExceeded { + span: Span::new(Location::new(1, 9), Location::new(1, 10)) + } + ); +} + +#[test] +fn parse_extract_missing_from_reports_location() { + assert_eq!( + all_dialects() + .parse_sql_statements_with_locations("SELECT EXTRACT(YEAR x)") + .unwrap_err() + .to_string(), + "sql parser error: Expected: 'FROM' or ',', found: x at Line: 1, Column: 21" + ); +} + +#[test] +fn peek_tokens_at_end_of_input_reports_location() { + let [eof] = all_dialects().one_of_identical_results(|dialect| { + let mut parser = Parser::new(dialect) + .try_with_sql("SELECT\n-- trailing\n") + .unwrap(); + parser.next_token(); + parser.peek_tokens_with_location() + }); + assert_eq!(eof.token, Token::EOF); + assert_eq!( + eof.span, + Span::new(Location::new(1, 7), Location::new(1, 7)) + ); +}
diff --git a/tests/sqlparser_custom_dialect.rs b/tests/sqlparser_custom_dialect.rs index c9fd75b..fe7421f 100644 --- a/tests/sqlparser_custom_dialect.rs +++ b/tests/sqlparser_custom_dialect.rs
@@ -152,11 +152,10 @@ let dialect = MyDialect {}; let sql = "SELECT MAP {1: 2}"; + let err = Parser::parse_sql(&dialect, sql).unwrap_err(); assert_eq!( - Parser::parse_sql(&dialect, sql), - Err(ParserError::ParserError( - "Expected: end of statement, found: { at Line: 1, Column: 12".to_string() - )) + err.to_string(), + "sql parser error: Expected: end of statement, found: { at Line: 1, Column: 12" ); Ok(()) } @@ -198,11 +197,10 @@ } } + let err = Parser::parse_sql(&WithoutPartitions {}, sql).unwrap_err(); assert_eq!( - Parser::parse_sql(&WithoutPartitions {}, sql), - Err(ParserError::ParserError( - "Expected: end of statement, found: PARTITION at Line: 1, Column: 25".to_string() - )) + err.to_string(), + "sql parser error: Expected: end of statement, found: PARTITION at Line: 1, Column: 25" ); Ok(()) } @@ -332,10 +330,9 @@ sql, &format!("{}", Parser::parse_sql(&dialect, sql).unwrap()[0]) ); + let err = Parser::parse_sql(&dialect, "SELECT transform(xs, lambda x : x + 1)").unwrap_err(); assert_eq!( - Parser::parse_sql(&dialect, "SELECT transform(xs, lambda x : x + 1)"), - Err(ParserError::ParserError( - "Expected: ), found: x at Line: 1, Column: 29".to_string() - )) + err.to_string(), + "sql parser error: Expected: ), found: x at Line: 1, Column: 29" ); }
diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index b9eb028..11822c7 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs
@@ -18,7 +18,6 @@ use sqlparser::ast::helpers::attached_token::AttachedToken; use sqlparser::ast::*; use sqlparser::dialect::{DatabricksDialect, GenericDialect}; -use sqlparser::parser::ParserError; use sqlparser::tokenizer::Span; use test_utils::*; @@ -86,7 +85,7 @@ let res = databricks().parse_sql_statements("SELECT EXISTS ("); assert_eq!( // TODO: improve this error message... - ParserError::ParserError("Expected: an expression, found: EOF".to_string()), + parser_error("Expected: an expression, found: EOF".to_string()), res.unwrap_err(), ); } @@ -294,7 +293,7 @@ for sql in &invalid_cases { assert_eq!( databricks().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + parser_error("Expected: identifier, found: EOF".to_string()), ); } } @@ -455,17 +454,15 @@ assert_eq!( databricks().parse_sql_statements("SELECT 1 FROM t1 FOR TIMESTAMP AS OF 'some_timestamp'"), - Err(ParserError::ParserError( - "Expected: one of UPDATE or SHARE, found: TIMESTAMP".to_string() + Err(parser_error( + "Expected: one of UPDATE or SHARE, found: TIMESTAMP" )) ); assert_eq!( all_dialects_where(|d| d.supports_table_versioning()) .parse_sql_statements("SELECT 1 FROM t1 VERSION AS OF 1 - 2"), - Err(ParserError::ParserError( - "Expected: end of statement, found: -".to_string() - )) + Err(parser_error("Expected: end of statement, found: -")) ); } @@ -528,13 +525,13 @@ databricks() .parse_sql_statements("OPTIMIZE my_table ZORDER BY") .unwrap_err(), - ParserError::ParserError("Expected: (, found: EOF".to_string()) + parser_error("Expected: (, found: EOF".to_string()) ); assert_eq!( databricks() .parse_sql_statements("OPTIMIZE my_table ZORDER BY ()") .unwrap_err(), - ParserError::ParserError("Expected: an expression, found: )".to_string()) + parser_error("Expected: an expression, found: )".to_string()) ); } @@ -743,9 +740,7 @@ assert_eq!( all_dialects_where(|d| !d.supports_cte_without_as()) .parse_sql_statements("WITH cte (SELECT 1) SELECT * FROM cte"), - Err(ParserError::ParserError( - "Expected: ), found: 1".to_string() - )) + Err(parser_error("Expected: ), found: 1")) ); } @@ -779,8 +774,6 @@ assert_eq!( TestedDialects::new(vec![Box::new(GenericDialect {})]) .parse_sql_statements("CREATE TABLE t (c ARRAY<STRING COLLATE UTF8_LCASE>)"), - Err(ParserError::ParserError( - "Expected: >, found: COLLATE".to_string() - )) + Err(parser_error("Expected: >, found: COLLATE")) ); }
diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 216d514..5ed8d2b 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs
@@ -19,12 +19,12 @@ mod test_utils; use helpers::attached_token::AttachedToken; -use sqlparser::tokenizer::Span; +use sqlparser::parser::ParserError; +use sqlparser::tokenizer::{Location, Span}; use test_utils::*; use sqlparser::ast::*; use sqlparser::dialect::{DuckDbDialect, GenericDialect}; -use sqlparser::parser::ParserError; fn duckdb() -> TestedDialects { TestedDialects::new(vec![Box::new(DuckDbDialect {})]) @@ -880,7 +880,7 @@ // missing comma separation let error_sql = "SELECT TRIM('xyz' 'a')"; assert_eq!( - ParserError::ParserError("Expected: ), found: 'a'".to_owned()), + parser_error("Expected: ), found: 'a'".to_owned()), duckdb().parse_sql_statements(error_sql).unwrap_err() ); } @@ -934,9 +934,10 @@ assert_eq!( duckdb().parse_sql_statements("SELECT a /* outer /* inner */ FROM t"), - Err(ParserError::TokenizerError( - "Unexpected EOF while in a multi-line comment at Line: 1, Column: 37".to_string() - )) + Err(ParserError::TokenizerError { + message: "Unexpected EOF while in a multi-line comment".to_string(), + span: Span::new(Location::new(1, 37), Location::new(1, 37)), + }) ); }
diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 21e76e2..61e3e1b 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs
@@ -27,7 +27,6 @@ Value, }; use sqlparser::dialect::{AnsiDialect, GenericDialect, HiveDialect}; -use sqlparser::parser::ParserError; use sqlparser::test_utils::*; #[test] @@ -150,7 +149,7 @@ ); assert_eq!( hive().parse_sql_statements(invalid_sql).unwrap_err(), - ParserError::ParserError("Expected: end of statement, found: COMMENT".to_string()) + parser_error("Expected: end of statement, found: COMMENT".to_string()) ); } @@ -201,28 +200,28 @@ hive_and_generic().parse_sql_statements( "CREATE TABLE db.table_name (a INT, b STRING) PARTITIONED BY (a INT, b STRING) CLUSTERED BY (a, b)" ).unwrap_err(), - ParserError::ParserError("Expected: INTO, found: EOF".to_string()) + parser_error("Expected: INTO, found: EOF".to_string()) ); // missing CLUSTER BY columns assert_eq!( hive_and_generic().parse_sql_statements( "CREATE TABLE db.table_name (a INT, b STRING) PARTITIONED BY (a INT, b STRING) CLUSTERED BY () INTO 4 BUCKETS" ).unwrap_err(), - ParserError::ParserError("Expected: identifier, found: )".to_string()) + parser_error("Expected: identifier, found: )".to_string()) ); // missing SORT BY columns assert_eq!( hive_and_generic().parse_sql_statements( "CREATE TABLE db.table_name (a INT, b STRING) PARTITIONED BY (a INT, b STRING) CLUSTERED BY (a, b) SORTED BY INTO 4 BUCKETS" ).unwrap_err(), - ParserError::ParserError("Expected: (, found: INTO".to_string()) + parser_error("Expected: (, found: INTO".to_string()) ); // missing number BUCKETS assert_eq!( hive_and_generic().parse_sql_statements( "CREATE TABLE db.table_name (a INT, b STRING) PARTITIONED BY (a INT, b STRING) CLUSTERED BY (a, b) SORTED BY (a ASC, b DESC) INTO" ).unwrap_err(), - ParserError::ParserError("Expected: a value, found: EOF".to_string()) + parser_error("Expected: a value, found: EOF".to_string()) ); } @@ -390,7 +389,7 @@ assert_eq!( hive().parse_sql_statements("SET hive.tez.java.opts = -"), - Err(ParserError::ParserError( + Err(parser_error( "Expected: variable value, found: EOF".to_string() )) ) @@ -434,15 +433,13 @@ assert_eq!( unsupported_dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError( - "Expected: an object type after CREATE, found: FUNCTION".to_string() - ) + parser_error("Expected: an object type after CREATE, found: FUNCTION".to_string()) ); let sql = "CREATE TEMPORARY FUNCTION mydb.myfunc AS 'org.random.class.Name' USING JAR"; assert_eq!( hive().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: literal string, found: EOF".to_string()), + parser_error("Expected: literal string, found: EOF".to_string()), ); }
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index e07d870..20c4a27 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs
@@ -84,8 +84,8 @@ let sql = "SELECT 1 FROM t1 FOR SYSTEM TIME AS OF 'some_timestamp'".to_string(); assert_eq!( ms().parse_sql_statements(&sql), - Err(ParserError::ParserError( - "Expected: one of UPDATE or SHARE, found: SYSTEM".to_owned() + Err(parser_error( + "Expected: one of UPDATE or SHARE, found: SYSTEM" )) ); } @@ -398,7 +398,7 @@ END\ "; assert_eq!( - ParserError::ParserError("Unparsable function body".to_owned()), + parser_error("Unparsable function body".to_owned()), ms().parse_sql_statements(create_multi_statement_tvf_without_table_definition) .unwrap_err() ); @@ -410,9 +410,7 @@ RETURN 'hi'\ "; assert_eq!( - ParserError::ParserError( - "Expected a subquery (or bare SELECT statement) after RETURN".to_owned() - ), + parser_error("Expected a subquery (or bare SELECT statement) after RETURN".to_owned()), ms().parse_sql_statements(create_inline_tvf_without_subquery_or_bare_select) .unwrap_err() ); @@ -962,9 +960,10 @@ fn dont_parse_trailing_for() { assert_eq!( ms().run_parser_method("SELECT * FROM foo FOR", |p| p.parse_query()), - Err(ParserError::ParserError( - "Expected: one of UPDATE or SHARE, found: EOF".to_owned() - )) + Err(ParserError::ParserError { + message: "Expected: one of UPDATE or SHARE, found: EOF".to_string(), + span: Span::new(Location::new(1, 22), Location::new(1, 22)), + }) ); } @@ -1340,7 +1339,7 @@ let error_sql = "SELECT CONVERT(INT, 'foo',) FROM T"; assert_eq!( - ParserError::ParserError("Expected: an expression, found: )".to_owned()), + parser_error("Expected: an expression, found: )".to_owned()), ms().parse_sql_statements(error_sql).unwrap_err() ); } @@ -1747,9 +1746,7 @@ let res = ms_and_generic().parse_sql_statements("WAITFOR '00:00:05'"); assert_eq!( res, - Err(ParserError::ParserError( - "Expected: DELAY or TIME, found: '00:00:05'".to_owned() - )) + Err(parser_error("Expected: DELAY or TIME, found: '00:00:05'")) ); } @@ -2663,7 +2660,7 @@ ] { assert_eq!( tsql().parse_sql_statements(sql), - Err(ParserError::ParserError(err.to_owned())) + Err(parser_error(err.to_owned())) ); } } @@ -2691,7 +2688,7 @@ ] { assert_eq!( tsql().parse_sql_statements(sql), - Err(ParserError::ParserError(err.to_owned())) + Err(parser_error(err.to_owned())) ); } } @@ -3004,3 +3001,15 @@ .expect_err("PROC should remain MSSQL-specific"); ms_and_generic().verified_stmt("SELECT proc FROM jobs"); } + +#[test] +fn parse_declare_without_at_sign_reports_location() { + let err = ms() + .parse_sql_statements_with_locations("DECLARE foo INT") + .unwrap_err(); + assert_eq!( + err.to_string(), + "sql parser error: Invalid MsSql variable declaration. at Line: 1, Column: 9" + ); + assert!(matches!(err, ParserError::ParserError { .. })); +}
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 8baa83b..520f29a 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs
@@ -26,8 +26,8 @@ use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, MySqlDialect}; use sqlparser::parser::{ParserError, ParserOptions}; -use sqlparser::tokenizer::Span; use sqlparser::tokenizer::Token; +use sqlparser::tokenizer::{Location, Span}; use test_utils::*; #[macro_use] @@ -539,8 +539,8 @@ ] { assert_eq!( mysql_and_generic().parse_sql_statements(sql), - Err(ParserError::ParserError( - "EXTENDED/FULL are not supported with this type of SHOW query".to_string() + Err(parser_error( + "EXTENDED/FULL are not supported with this type of SHOW query" )) ); } @@ -1365,7 +1365,7 @@ r"CREATE TABLE foo (id INT(11)) ENGINE = InnoDB AS SELECT 1 DEFAULT CHARSET = utf8mb3"; assert!(matches!( mysql_and_generic().parse_sql_statements(sql), - Err(ParserError::ParserError(_)) + Err(ParserError::ParserError { .. }) )); } @@ -1665,17 +1665,19 @@ let sql = r"SELECT 'I\'m not fine\'"; assert_eq!( mysql().parse_sql_statements(sql), - Err(ParserError::TokenizerError( - "Unterminated string literal at Line: 1, Column: 8".to_string() - )) + Err(ParserError::TokenizerError { + message: "Unterminated string literal".to_string(), + span: Span::new(Location::new(1, 8), Location::new(1, 8)), + }) ); let sql = r"SELECT 'I\\'m not fine'"; assert_eq!( mysql().parse_sql_statements(sql), - Err(ParserError::TokenizerError( - "Unterminated string literal at Line: 1, Column: 23".to_string() - )) + Err(ParserError::TokenizerError { + message: "Unterminated string literal".to_string(), + span: Span::new(Location::new(1, 23), Location::new(1, 23)), + }) ); } @@ -1948,15 +1950,15 @@ mysql_and_generic().verified_stmt("CREATE TABLE t (a INT(10) UNSIGNED ZEROFILL)"); assert_eq!( mysql_and_generic().parse_sql_statements("CREATE TABLE t (a VARCHAR(10) ZEROFILL)"), - Err(ParserError::ParserError( - "Expected: ',' or ')' after column definition, found: ZEROFILL".to_string() + Err(parser_error( + "Expected: ',' or ')' after column definition, found: ZEROFILL" )) ); assert_eq!( all_dialects_except(|d| d.supports_data_type_zerofill_suffix()) .parse_sql_statements("CREATE TABLE t (a INT ZEROFILL)"), - Err(ParserError::ParserError( - "Expected: ',' or ')' after column definition, found: ZEROFILL".to_string() + Err(parser_error( + "Expected: ',' or ')' after column definition, found: ZEROFILL" )) ); } @@ -2250,7 +2252,7 @@ let sql = r"INSERT INTO `table` (`date`) VALUES ('2024-01-01') AS `alias` ()"; assert!(matches!( mysql_and_generic().parse_sql_statements(sql), - Err(ParserError::ParserError(_)) + Err(ParserError::ParserError { .. }) )); let sql = r"INSERT INTO `table` (`id`, `date`) VALUES (1, '2024-01-01') AS `alias` (`mek_id`, `mek_date`)"; @@ -3637,8 +3639,8 @@ let sql = "CREATE TABLE foo (bar INT, CONSTRAINT constr INDEX index (bar))"; assert_eq!( mysql_and_generic().parse_sql_statements(sql), - Err(ParserError::ParserError( - "Expected: PRIMARY, UNIQUE, FOREIGN, CHECK, or EXCLUDE, found: INDEX".to_string() + Err(parser_error( + "Expected: PRIMARY, UNIQUE, FOREIGN, CHECK, or EXCLUDE, found: INDEX" )) ); @@ -3808,7 +3810,7 @@ let err = mysql() .parse_sql_statements("SELECT 5 DIV") .expect_err("expected an error"); - assert_matches!(err, ParserError::ParserError(_)); + assert_matches!(err, ParserError::ParserError { .. }); } #[test] @@ -4764,7 +4766,7 @@ ] { assert_eq!( mysql().parse_sql_statements(sql), - Err(ParserError::ParserError(err.to_string())) + Err(parser_error(err)) ); } } @@ -5029,7 +5031,7 @@ ] { assert_eq!( mysql_and_generic().parse_sql_statements(sql), - Err(ParserError::ParserError(missing.to_string())) + Err(parser_error(missing)) ); } } @@ -5074,7 +5076,7 @@ let err = mysql_and_generic() .parse_sql_statements("SELECT * FROM employees PARTITION") .expect_err("expected an error"); - assert_matches!(err, ParserError::ParserError(_)); + assert_matches!(err, ParserError::ParserError { .. }); } #[test] @@ -5191,7 +5193,7 @@ ] { assert_eq!( mysql_and_generic().parse_sql_statements(sql), - Err(ParserError::ParserError(err.to_string())), + Err(parser_error(err)), "{sql}" ); } @@ -5202,3 +5204,14 @@ mysql_and_generic().verified_stmt("CREATE TABLE t (a INT) ENGINE = `InnoDB`"); mysql_and_generic().verified_stmt("CREATE TABLE t (a INT) ENGINE = InnoDB"); } + +#[test] +fn parse_alter_role_unsupported_reports_location() { + assert_eq!( + mysql() + .parse_sql_statements_with_locations("ALTER ROLE r") + .unwrap_err() + .to_string(), + "sql parser error: ALTER ROLE is only support for PostgreSqlDialect, MsSqlDialect at Line: 1, Column: 7" + ); +}
diff --git a/tests/sqlparser_oracle.rs b/tests/sqlparser_oracle.rs index 888778e..b0dc982 100644 --- a/tests/sqlparser_oracle.rs +++ b/tests/sqlparser_oracle.rs
@@ -26,10 +26,9 @@ Statement, TableAliasWithoutColumns, TableObject, Value, ValueWithSpan, }, dialect::OracleDialect, - parser::ParserError, tokenizer::Span, }; -use test_utils::{all_dialects_where, expr_from_projection, number, TestedDialects}; +use test_utils::{all_dialects_where, expr_from_projection, number, parser_error, TestedDialects}; mod test_utils; @@ -215,27 +214,30 @@ // invalid quote delimiter for q in [' ', '\t', '\r', '\n'] { assert_eq!( - dialect.parse_sql_statements(&format!("SELECT Q'{q}abc{q}' FROM dual")), - Err(ParserError::TokenizerError( - "Invalid space, tab, newline, or EOF after 'Q'' at Line: 1, Column: 10".into() - )), + dialect + .parse_sql_statements(&format!("SELECT Q'{q}abc{q}' FROM dual")) + .unwrap_err() + .to_string(), + "sql parser error: Invalid space, tab, newline, or EOF after 'Q'' at Line: 1, Column: 10", "with quote char {q:?}" ); } // invalid eof after quote assert_eq!( - dialect.parse_sql_statements("SELECT Q'"), - Err(ParserError::TokenizerError( - "Invalid space, tab, newline, or EOF after 'Q'' at Line: 1, Column: 10".into() - )), + dialect + .parse_sql_statements("SELECT Q'") + .unwrap_err() + .to_string(), + "sql parser error: Invalid space, tab, newline, or EOF after 'Q'' at Line: 1, Column: 10", "with EOF quote char" ); // unterminated string assert_eq!( - dialect.parse_sql_statements("SELECT Q'|asdfa...."), - Err(ParserError::TokenizerError( - "Unterminated string literal at Line: 1, Column: 9".into() - )), + dialect + .parse_sql_statements("SELECT Q'|asdfa....") + .unwrap_err() + .to_string(), + "sql parser error: Unterminated string literal at Line: 1, Column: 9", "with EOF quote char" ); } @@ -497,8 +499,8 @@ let sql = "INSERT INTO t default SELECT 'a' FROM dual"; assert_eq!( oracle_dialect.parse_sql_statements(sql), - Err(ParserError::ParserError( - "Expected: SELECT, VALUES, or a subquery in the query body, found: default".into() + Err(parser_error( + "Expected: SELECT, VALUES, or a subquery in the query body, found: default" )) );
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 1e00288..0385e63 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs
@@ -306,7 +306,7 @@ assert!(matches!( pg().parse_sql_statements("CREATE SEQUENCE foo INCREMENT 1 NO MINVALUE NO"), - Err(ParserError::ParserError(_)) + Err(ParserError::ParserError { .. }) )); } @@ -662,7 +662,7 @@ ] { let result = dialects.parse_sql_statements(sql); assert_eq!( - ParserError::ParserError(expected_message.to_string()), + parser_error(expected_message.to_string()), result.unwrap_err() ); } @@ -672,8 +672,8 @@ assert_eq!( all_dialects_where(|d| !d.supports_exclude_constraint()) .parse_sql_statements("CREATE TABLE t (col INT, EXCLUDE USING gist (col WITH =))"), - Err(ParserError::ParserError( - "Expected: ',' or ')' after column definition, found: gist".to_string() + Err(parser_error( + "Expected: ',' or ')' after column definition, found: gist" )) ); } @@ -1186,7 +1186,7 @@ "ALTER TABLE t ALTER COLUMN id ADD GENERATED ( INCREMENT 1 MINVALUE 1 )", ); assert_eq!( - ParserError::ParserError("Expected: AS, found: (".to_string()), + parser_error("Expected: AS, found: (".to_string()), res.unwrap_err() ); @@ -1194,14 +1194,14 @@ "ALTER TABLE t ALTER COLUMN id ADD GENERATED AS IDENTITY ( INCREMENT )", ); assert_eq!( - ParserError::ParserError("Expected: a value, found: )".to_string()), + parser_error("Expected: a value, found: )".to_string()), res.unwrap_err() ); let res = pg().parse_sql_statements("ALTER TABLE t ALTER COLUMN id ADD GENERATED AS IDENTITY ("); assert_eq!( - ParserError::ParserError("Expected: ), found: EOF".to_string()), + parser_error("Expected: ), found: EOF".to_string()), res.unwrap_err() ); } @@ -1310,13 +1310,15 @@ let res = pg().parse_sql_statements("ALTER TABLE tab OWNER TO CREATE FOO"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: FOO".to_string()), + parser_error("Expected: end of statement, found: FOO".to_string()), res.unwrap_err() ); let res = pg().parse_sql_statements("ALTER TABLE tab OWNER TO 4"); assert_eq!( - ParserError::ParserError("Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. sql parser error: Expected: identifier, found: 4".to_string()), + parser_error( + "Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO, found: 4" + ), res.unwrap_err() ); } @@ -1368,25 +1370,25 @@ fn parse_bad_if_not_exists() { let res = pg().parse_sql_statements("CREATE TABLE NOT EXISTS uk_cities ()"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: EXISTS".to_string()), + parser_error("Expected: end of statement, found: EXISTS".to_string()), res.unwrap_err() ); let res = pg().parse_sql_statements("CREATE TABLE IF EXISTS uk_cities ()"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: EXISTS".to_string()), + parser_error("Expected: end of statement, found: EXISTS".to_string()), res.unwrap_err() ); let res = pg().parse_sql_statements("CREATE TABLE IF uk_cities ()"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: uk_cities".to_string()), + parser_error("Expected: end of statement, found: uk_cities".to_string()), res.unwrap_err() ); let res = pg().parse_sql_statements("CREATE TABLE IF NOT uk_cities ()"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: NOT".to_string()), + parser_error("Expected: end of statement, found: NOT".to_string()), res.unwrap_err() ); } @@ -1678,7 +1680,7 @@ fn parse_copy_from_error() { let res = pg().parse_sql_statements("COPY (SELECT 42 AS a, 'hello' AS b) FROM 'query.csv'"); assert_eq!( - ParserError::ParserError("COPY ... FROM does not support query as a source".to_string()), + parser_error("COPY ... FROM does not support query as a source".to_string()), res.unwrap_err() ); } @@ -1999,21 +2001,19 @@ assert_eq!( pg_and_generic().parse_sql_statements("SET"), - Err(ParserError::ParserError( - "Expected: identifier, found: EOF".to_string() - )), + Err(parser_error("Expected: identifier, found: EOF".to_string())), ); assert_eq!( pg_and_generic().parse_sql_statements("SET a b"), - Err(ParserError::ParserError( + Err(parser_error( "Expected: equals sign or TO, found: b".to_string() )), ); assert_eq!( pg_and_generic().parse_sql_statements("SET a ="), - Err(ParserError::ParserError( + Err(parser_error( "Expected: variable value, found: EOF".to_string() )), ); @@ -3377,7 +3377,7 @@ fn parse_create_table_with_empty_inherits_fails() { assert!(matches!( pg().parse_sql_statements("CREATE TABLE child_table (child_column INT) INHERITS ()"), - Err(ParserError::ParserError(_)) + Err(ParserError::ParserError { .. }) )); } @@ -4444,7 +4444,7 @@ fn parse_operator_empty_parens_rejected() { let result = pg_and_generic().parse_sql_statements("SELECT a OPERATOR() b"); assert_eq!( - ParserError::ParserError("Expected: operator name, found: )".to_string()), + parser_error("Expected: operator name, found: )".to_string()), result.unwrap_err() ); } @@ -4539,7 +4539,7 @@ let sql = format!("CREATE ROLE abc {negatable_kw} NO{negatable_kw}"); assert_eq!( pg().parse_sql_statements(&sql), - Err(ParserError::ParserError(format!( + Err(parser_error(format!( "Found multiple {negatable_kw} or NO{negatable_kw}" ))) ); @@ -5134,7 +5134,7 @@ let incorrect_sql = "CREATE FUNCTION add(function(struct<a,b> int64), b INTEGER) RETURNS INTEGER LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE AS 'select $1 + $2;'"; assert_eq!( pg().parse_sql_statements(incorrect_sql).unwrap_err(), - ParserError::ParserError("Expected: type modifiers, found: <".to_string()) + parser_error("Expected: type modifiers, found: <") ); } @@ -5273,9 +5273,7 @@ let sql = "CREATE FUNCTION add(INTEGER, INTEGER) RETURNS INTEGER LANGUAGE SQL PARALLEL BLAH AS 'select $1 + $2;'"; assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError( - "Expected: one of UNSAFE | RESTRICTED | SAFE, found: BLAH".to_string() - ) + parser_error("Expected: one of UNSAFE | RESTRICTED | SAFE, found: BLAH") ); } @@ -5576,13 +5574,13 @@ let res = pg().parse_sql_statements("DROP PROCEDURE testproc DROP"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: DROP".to_string()), + parser_error("Expected: end of statement, found: DROP".to_string()), res.unwrap_err() ); let res = pg().parse_sql_statements("DROP PROCEDURE testproc SET NULL"); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: SET".to_string()), + parser_error("Expected: end of statement, found: SET".to_string()), res.unwrap_err() ); } @@ -5677,23 +5675,28 @@ #[test] fn parse_incorrect_dollar_quoted_string() { - for (sql, expected) in [ + for (sql, expected, location) in [ ( "SELECT $x$hello$$", - "Unterminated dollar-quoted, expected $ at Line: 1, Column: 18", + "Unterminated dollar-quoted, expected $", + (1, 18), ), ( "SELECT $hello$$", - "Unterminated dollar-quoted, expected $ at Line: 1, Column: 16", + "Unterminated dollar-quoted, expected $", + (1, 16), ), - ( - "SELECT $$$", - "Unterminated dollar-quoted string at Line: 1, Column: 11", - ), + ("SELECT $$$", "Unterminated dollar-quoted string", (1, 11)), ] { assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::TokenizerError(expected.to_string()) + ParserError::TokenizerError { + message: expected.to_string(), + span: Span::new( + Location::new(location.0, location.1), + Location::new(location.0, location.1), + ), + } ); } } @@ -6061,7 +6064,7 @@ let res = pg().parse_sql_statements("CREATE UNLOGGED VIEW v AS SELECT 1"); assert_eq!( - ParserError::ParserError("Expected: an object type after CREATE, found: UNLOGGED".into()), + parser_error("Expected: an object type after CREATE, found: UNLOGGED"), res.unwrap_err() ); } @@ -7814,7 +7817,7 @@ assert_eq!( pg().parse_sql_statements(&format!("CREATE OPERATOR + (FUNCTION = f, {field})")) .unwrap_err(), - ParserError::ParserError(format!( + parser_error(format!( "Duplicate or unexpected keyword {keyword} in CREATE OPERATOR" )) ); @@ -7824,29 +7827,23 @@ assert_eq!( pg().parse_sql_statements("CREATE OPERATOR + (LEFTARG = INT4)") .unwrap_err(), - ParserError::ParserError("CREATE OPERATOR requires FUNCTION parameter".to_string()) + parser_error("CREATE OPERATOR requires FUNCTION parameter") ); // Test empty parameter list error assert_eq!( pg().parse_sql_statements("CREATE OPERATOR + ()").unwrap_err(), - ParserError::ParserError( - "Expected: one of FUNCTION or PROCEDURE or LEFTARG or RIGHTARG or COMMUTATOR or NEGATOR or RESTRICT or JOIN or HASHES or MERGES, found: )".to_string() - ) + parser_error("Expected: one of FUNCTION or PROCEDURE or LEFTARG or RIGHTARG or COMMUTATOR or NEGATOR or RESTRICT or JOIN or HASHES or MERGES, found: )") ); // Test nested empty parentheses error assert_eq!( pg().parse_sql_statements("CREATE OPERATOR > (()").unwrap_err(), - ParserError::ParserError( - "Expected: one of FUNCTION or PROCEDURE or LEFTARG or RIGHTARG or COMMUTATOR or NEGATOR or RESTRICT or JOIN or HASHES or MERGES, found: (".to_string() - ) + parser_error("Expected: one of FUNCTION or PROCEDURE or LEFTARG or RIGHTARG or COMMUTATOR or NEGATOR or RESTRICT or JOIN or HASHES or MERGES, found: (") ); assert_eq!( pg().parse_sql_statements("CREATE OPERATOR > ())").unwrap_err(), - ParserError::ParserError( - "Expected: one of FUNCTION or PROCEDURE or LEFTARG or RIGHTARG or COMMUTATOR or NEGATOR or RESTRICT or JOIN or HASHES or MERGES, found: )".to_string() - ) + parser_error("Expected: one of FUNCTION or PROCEDURE or LEFTARG or RIGHTARG or COMMUTATOR or NEGATOR or RESTRICT or JOIN or HASHES or MERGES, found: )") ); } @@ -7939,14 +7936,14 @@ let sql = "DROP OPERATOR (INTEGER, INTEGER)"; assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: (, found: INTEGER".to_string()) + parser_error("Expected: (, found: INTEGER") ); // Test error: DROP OPERATOR IF EXISTS with no operators let sql = "DROP OPERATOR IF EXISTS (INTEGER, INTEGER)"; assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: (, found: INTEGER".to_string()) + parser_error("Expected: (, found: INTEGER") ); } @@ -8357,7 +8354,7 @@ // OWNER TO with invalid owner ( "ALTER OPERATOR FAMILY my_ops USING btree OWNER TO 123invalid", - "Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. sql parser error: Expected: identifier, found: 123" + "Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO, found: 123" ), // SET SCHEMA with invalid schema name ("ALTER OPERATOR FAMILY my_ops USING btree SET SCHEMA 123invalid", "Expected: identifier, found: 123"), @@ -8398,7 +8395,7 @@ ] { assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError(expected.to_string()) + parser_error(expected) ); } @@ -8408,7 +8405,7 @@ "ALTER OPERATOR FAMILY my_ops USING btree ADD FUNCTION 1 () func" ) .unwrap_err(), - ParserError::ParserError("Expected: identifier, found: )".to_string()) + parser_error("Expected: identifier, found: )") ); for (sql, expected) in [ @@ -8430,7 +8427,7 @@ ] { assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError(expected.to_string()) + parser_error(expected) ); } } @@ -8521,7 +8518,7 @@ // Missing owner for OWNER TO ( "ALTER OPERATOR CLASS int_ops USING btree OWNER TO", - "Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. sql parser error: Expected: identifier, found: EOF" + "Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO, found: EOF" ), // Missing schema for SET SCHEMA ("ALTER OPERATOR CLASS int_ops USING btree SET SCHEMA", "Expected: identifier, found: EOF"), @@ -8530,7 +8527,7 @@ // Invalid owner ( "ALTER OPERATOR CLASS int_ops USING btree OWNER TO 123invalid", - "Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO. sql parser error: Expected: identifier, found: 123" + "Expected: CURRENT_USER, CURRENT_ROLE, SESSION_USER or identifier after OWNER TO, found: 123" ), // Invalid schema name ("ALTER OPERATOR CLASS int_ops USING btree SET SCHEMA 123invalid", "Expected: identifier, found: 123"), @@ -8561,7 +8558,7 @@ ] { assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError(expected.to_string()) + parser_error(expected) ); } } @@ -8792,7 +8789,7 @@ ] { assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError(expected.to_string()) + parser_error(expected) ); } } @@ -8817,16 +8814,14 @@ pg() .parse_sql_statements(r#"CREATE TEXT SEARCH "DICTIONARY" d (template = simple)"#) .unwrap_err(), - ParserError::ParserError( - "Expected: one of DICTIONARY or CONFIGURATION or TEMPLATE or PARSER, found: \"DICTIONARY\"".to_string() - ) + parser_error("Expected: one of DICTIONARY or CONFIGURATION or TEMPLATE or PARSER, found: \"DICTIONARY\"") ); // CREATE options are key-value pairs in PostgreSQL syntax. assert_eq!( pg().parse_sql_statements("CREATE TEXT SEARCH DICTIONARY d (template)") .unwrap_err(), - ParserError::ParserError("Expected: =, found: )".to_string()) + parser_error("Expected: =, found: )") ); } @@ -8915,14 +8910,14 @@ let sql = "DROP OPERATOR FAMILY USING btree"; assert_eq!( pg_and_generic().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: USING, found: btree".to_string()) + parser_error("Expected: USING, found: btree") ); // Test error: DROP OPERATOR FAMILY IF EXISTS with no names let sql = "DROP OPERATOR FAMILY IF EXISTS USING btree"; assert_eq!( pg_and_generic().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: USING, found: btree".to_string()) + parser_error("Expected: USING, found: btree") ); } @@ -8984,14 +8979,14 @@ let sql = "DROP OPERATOR CLASS USING btree"; assert_eq!( pg_and_generic().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: USING, found: btree".to_string()) + parser_error("Expected: USING, found: btree") ); // Test error: DROP OPERATOR CLASS IF EXISTS with no names let sql = "DROP OPERATOR CLASS IF EXISTS USING btree"; assert_eq!( pg_and_generic().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: USING, found: btree".to_string()) + parser_error("Expected: USING, found: btree") ); } @@ -9251,7 +9246,7 @@ "CREATE OPERATOR CLASS test_ops FOR TYPE INT4 USING btree AS FUNCTION 1 cas_cmp(()" ) .unwrap_err(), - ParserError::ParserError("Expected: a data type name, found: (".to_string()) + parser_error("Expected: a data type name, found: (") ); } @@ -9485,7 +9480,7 @@ ] { assert_eq!( pg_and_generic().parse_sql_statements(sql), - Err(ParserError::ParserError(err.to_string())), + Err(parser_error(err)), "{sql}" ); } @@ -9614,7 +9609,7 @@ // Without the parens Display would invent a `()` the input never had. assert!(matches!( pg_and_generic().parse_sql_statements("CREATE FOREIGN TABLE ft SERVER s"), - Err(ParserError::ParserError(_)) + Err(ParserError::ParserError { .. }) )); // An empty list is still legal PostgreSQL. @@ -9648,7 +9643,7 @@ assert!(matches!( pg_and_generic() .parse_sql_statements("CREATE OR REPLACE FOREIGN TABLE ft (a INT) SERVER s"), - Err(ParserError::ParserError(_)) + Err(ParserError::ParserError { .. }) )); } @@ -9928,9 +9923,7 @@ pg_and_generic().parse_sql_statements( "MERGE INTO target USING source ON target.id = source.id WHEN MATCHED THEN DO UPDATE" ), - Err(ParserError::ParserError( - "Expected: NOTHING, found: UPDATE".into() - )) + Err(parser_error("Expected: NOTHING, found: UPDATE")) ); } @@ -10046,7 +10039,7 @@ ] { assert_eq!( pg_and_generic().parse_sql_statements(sql), - Err(ParserError::ParserError(err.to_string())), + Err(parser_error(err)), "{sql}" ); } @@ -10079,10 +10072,8 @@ pg().one_statement_parses_to("SELECT @a", "SELECT @ a"); let err = pg().parse_sql_statements("SELECT @-2").unwrap_err(); assert_eq!( - ParserError::TokenizerError( - "Expected a valid binary operator after '@-' at Line: 1, Column: 10".to_string(), - ), - err + err.to_string(), + "sql parser error: Expected a valid binary operator after '@-' at Line: 1, Column: 10" ); } @@ -10115,7 +10106,7 @@ let err = pg().parse_sql_statements("SELECT a!!").unwrap_err(); assert_eq!( - ParserError::ParserError("Expected: end of statement, found: !!".to_string()), + parser_error("Expected: end of statement, found: !!".to_string()), err ); } @@ -10132,7 +10123,7 @@ let sql = "SELECT * FROM @stage"; assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: identifier, found: @".to_string()), + parser_error("Expected: identifier, found: @".to_string()), ); } @@ -10196,9 +10187,22 @@ ] { assert_eq!( pg().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError(expected.to_string()), + parser_error(expected.to_string()), "{sql}" ); } pg().verified_stmt(r#"SELECT a AS "x" FROM t AS "y""#); } + +#[test] +fn parse_create_function_repeated_clause_reports_location() { + assert_eq!( + pg_and_generic() + .parse_sql_statements_with_locations( + "CREATE FUNCTION f() RETURNS INT LANGUAGE sql LANGUAGE sql AS 'select 1'" + ) + .unwrap_err() + .to_string(), + "sql parser error: LANGUAGE specified more than once at Line: 1, Column: 46" + ); +}
diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 5bf1a89..667d213 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs
@@ -18,7 +18,7 @@ #[macro_use] mod test_utils; -use sqlparser::tokenizer::Span; +use sqlparser::tokenizer::{Location, Span}; use test_utils::*; use sqlparser::ast::*; @@ -390,9 +390,10 @@ // invalid query assert_eq!( redshift().parse_sql_statements(r#"SELECT 1 AS ["1]"#), - Err(ParserError::TokenizerError( - "Expected close delimiter '\"' before EOF. at Line: 1, Column: 14".to_string() - )) + Err(ParserError::TokenizerError { + message: "Expected close delimiter '\"' before EOF.".to_string(), + span: Span::new(Location::new(1, 14), Location::new(1, 14)), + }) ); }
diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 6c8c73c..4c4ae69 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs
@@ -449,14 +449,14 @@ fn test_snowflake_create_invalid_local_global_table() { assert_eq!( snowflake().parse_sql_statements("CREATE LOCAL GLOBAL TABLE my_table (a INT)"), - Err(ParserError::ParserError( + Err(parser_error( "Expected: an SQL statement, found: LOCAL".to_string() )) ); assert_eq!( snowflake().parse_sql_statements("CREATE GLOBAL LOCAL TABLE my_table (a INT)"), - Err(ParserError::ParserError( + Err(parser_error( "Expected: an SQL statement, found: GLOBAL".to_string() )) ); @@ -466,14 +466,14 @@ fn test_snowflake_create_invalid_temporal_table() { assert_eq!( snowflake().parse_sql_statements("CREATE TEMP TEMPORARY TABLE my_table (a INT)"), - Err(ParserError::ParserError( + Err(parser_error( "Expected: an object type after CREATE, found: TEMPORARY".to_string() )) ); assert_eq!( snowflake().parse_sql_statements("CREATE TEMP TRANSIENT TABLE my_table (a INT)"), - Err(ParserError::ParserError( + Err(parser_error( "Expected: an object type after CREATE, found: TRANSIENT".to_string() )) ); @@ -483,7 +483,7 @@ fn test_snowflake_create_invalid_temporal_file_format() { assert_eq!( snowflake().parse_sql_statements("CREATE TEMPORARY VOLATILE FILE FORMAT my_fmt"), - Err(ParserError::ParserError( + Err(parser_error( "Expected: an object type after CREATE, found: FILE".to_string() )) ); @@ -1031,7 +1031,7 @@ fn test_snowflake_create_iceberg_table_without_location() { let res = snowflake().parse_sql_statements("CREATE ICEBERG TABLE my_table (a INT)"); assert_eq!( - ParserError::ParserError("BASE_LOCATION is required for ICEBERG tables".to_string()), + parser_error("BASE_LOCATION is required for ICEBERG tables".to_string()), res.unwrap_err() ); } @@ -1068,9 +1068,7 @@ assert_eq!( snowflake_and_generic() .parse_sql_statements("CREATE OR REPLACE VIEW v COMMENT 'hello, world' AS SELECT 1"), - Err(ParserError::ParserError( - "Expected: =, found: 'hello, world'".to_string() - )) + Err(parser_error("Expected: =, found: 'hello, world'")) ); } @@ -1270,7 +1268,7 @@ let res = snowflake().parse_sql_statements("SELECT * FROM (a b) c"); assert_eq!( - ParserError::ParserError("duplicate alias b".to_string()), + parser_error("duplicate alias b".to_string()), res.unwrap_err() ); } @@ -1778,13 +1776,13 @@ let error_sql = "DECLARE c1 CURSOR SELECT id FROM invoices"; assert_eq!( - ParserError::ParserError("Expected: FOR, found: SELECT".to_owned()), + parser_error("Expected: FOR, found: SELECT".to_owned()), snowflake().parse_sql_statements(error_sql).unwrap_err() ); let error_sql = "DECLARE c1 CURSOR res"; assert_eq!( - ParserError::ParserError("Expected: FOR, found: res".to_owned()), + parser_error("Expected: FOR, found: res".to_owned()), snowflake().parse_sql_statements(error_sql).unwrap_err() ); } @@ -1832,13 +1830,13 @@ let error_sql = "DECLARE res RESULTSET DEFAULT"; assert_eq!( - ParserError::ParserError("Expected: an expression, found: EOF".to_owned()), + parser_error("Expected: an expression, found: EOF".to_owned()), snowflake().parse_sql_statements(error_sql).unwrap_err() ); let error_sql = "DECLARE res RESULTSET :="; assert_eq!( - ParserError::ParserError("Expected: an expression, found: EOF".to_owned()), + parser_error("Expected: an expression, found: EOF".to_owned()), snowflake().parse_sql_statements(error_sql).unwrap_err() ); } @@ -1924,19 +1922,19 @@ let error_sql = "DECLARE profit INT 2"; assert_eq!( - ParserError::ParserError("Expected: end of statement, found: 2".to_owned()), + parser_error("Expected: end of statement, found: 2".to_owned()), snowflake().parse_sql_statements(error_sql).unwrap_err() ); let error_sql = "DECLARE profit INT DEFAULT"; assert_eq!( - ParserError::ParserError("Expected: an expression, found: EOF".to_owned()), + parser_error("Expected: an expression, found: EOF".to_owned()), snowflake().parse_sql_statements(error_sql).unwrap_err() ); let error_sql = "DECLARE profit DEFAULT"; assert_eq!( - ParserError::ParserError("Expected: an expression, found: EOF".to_owned()), + parser_error("Expected: an expression, found: EOF".to_owned()), snowflake().parse_sql_statements(error_sql).unwrap_err() ); } @@ -1971,7 +1969,7 @@ let error_sql = "DECLARE profit DEFAULT 42 c1 CURSOR FOR res;"; assert_eq!( - ParserError::ParserError("Expected: end of statement, found: c1".to_owned()), + parser_error("Expected: end of statement, found: c1".to_owned()), snowflake().parse_sql_statements(error_sql).unwrap_err() ); } @@ -2892,7 +2890,7 @@ // missing comma separation let error_sql = "SELECT TRIM('xyz' 'a')"; assert_eq!( - ParserError::ParserError("Expected: ), found: 'a'".to_owned()), + parser_error("Expected: ), found: 'a'".to_owned()), snowflake().parse_sql_statements(error_sql).unwrap_err() ); } @@ -3376,7 +3374,7 @@ for sql in &invalid_cases { assert_eq!( snowflake().parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + parser_error("Expected: identifier, found: EOF".to_string()), ); } @@ -3457,7 +3455,10 @@ let sql = format!("SELECT * FROM {l_parens}a.b.c{r_parens}"); let parsed = snowflake_with_recursion_limit(max_nesting_level).parse_sql_statements(sql.as_str()); - assert_eq!(parsed.err(), Some(ParserError::RecursionLimitExceeded)); + assert!(matches!( + parsed, + Err(ParserError::RecursionLimitExceeded { .. }) + )); } #[test] @@ -4108,7 +4109,7 @@ ] { assert_eq!( snowflake().parse_sql_statements(&format!("SELECT 1, {kw}")), - Err(ParserError::ParserError(err.to_string())) + Err(parser_error(err.to_string())) ); } } @@ -4143,7 +4144,7 @@ ] { assert_eq!( snowflake().parse_sql_statements(&format!("SELECT 1 {kw}")), - Err(ParserError::ParserError(err.to_string())) + Err(parser_error(err.to_string())) ); } @@ -4240,7 +4241,7 @@ ] { assert_eq!( snowflake().parse_sql_statements(&format!("SELECT * FROM tbl {kw}")), - Err(ParserError::ParserError(err.to_string())) + Err(parser_error(err.to_string())) ); } @@ -4271,9 +4272,7 @@ // ORDER is reserved assert_eq!( snowflake().parse_sql_statements("SELECT * FROM tbl, order"), - Err(ParserError::ParserError( - "Expected: end of statement, found: order".to_string() - )) + Err(parser_error("Expected: end of statement, found: order")) ); } @@ -4834,15 +4833,15 @@ snowflake().parse_sql_statements( "CREATE TABLE IDENTIFIER('db1').IDENTIFIER('sc1').IDENTIFIER('tbl') (id INT)" ), - Err(ParserError::ParserError( - "Expected: column name or constraint definition, found: 'sc1'".to_string() + Err(parser_error( + "Expected: column name or constraint definition, found: 'sc1'" )) ); assert_eq!( snowflake() .parse_sql_statements("CREATE TABLE IDENTIFIER('db1')..IDENTIFIER('tbl') (id INT)"), - Err(ParserError::ParserError( - "Expected: column name or constraint definition, found: 'tbl'".to_string() + Err(parser_error( + "Expected: column name or constraint definition, found: 'tbl'" )) ); } @@ -4888,9 +4887,7 @@ for sql in ["CREATE DATABASE", "CREATE DATABASE my_db CLONE"] { assert_eq!( snowflake().parse_sql_statements(sql), - Err(ParserError::ParserError( - "Expected: identifier, found: EOF".to_string() - )), + Err(parser_error("Expected: identifier, found: EOF")), "{sql}" ); } @@ -5038,8 +5035,8 @@ assert_eq!( snowflake().parse_sql_statements("CREATE TABLE t (o OBJECT(city VARCHAR)"), - Err(ParserError::ParserError( - "Expected: ',' or ')' after column definition, found: EOF".to_string() + Err(parser_error( + "Expected: ',' or ')' after column definition, found: EOF" )) ); @@ -5080,24 +5077,35 @@ snowflake() .parse_sql_statements("SELECT * FROM @") .unwrap_err(), - ParserError::ParserError("Expected: stage name identifier, found: EOF".to_string()), + parser_error("Expected: stage name identifier, found: EOF".to_string()), ); assert_eq!( snowflake() .parse_sql_statements("SELECT * FROM @;") .unwrap_err(), - ParserError::ParserError("Expected: stage name identifier, found: ;".to_string()), + parser_error("Expected: stage name identifier, found: ;".to_string()), ); assert_eq!( snowflake() .parse_sql_statements("SELECT * FROM @, item") .unwrap_err(), - ParserError::ParserError("Expected: stage name identifier, found: ,".to_string()), + parser_error("Expected: stage name identifier, found: ,".to_string()), ); assert_eq!( snowflake() .parse_sql_statements("SELECT * FROM @.stage") .unwrap_err(), - ParserError::ParserError("Expected: stage name identifier, found: .".to_string()), + parser_error("Expected: stage name identifier, found: .".to_string()), + ); +} + +#[test] +fn parse_iceberg_table_without_base_location_reports_location() { + assert_eq!( + snowflake() + .parse_sql_statements_with_locations("CREATE ICEBERG TABLE t (a INT)") + .unwrap_err() + .to_string(), + "sql parser error: BASE_LOCATION is required for ICEBERG tables at Line: 1, Column: 31" ); }
diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 2e60c75..627792c 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs
@@ -30,7 +30,7 @@ use sqlparser::ast::Value::Placeholder; use sqlparser::ast::*; use sqlparser::dialect::{AnsiDialect, GenericDialect, SQLiteDialect}; -use sqlparser::parser::{ParserError, ParserOptions}; +use sqlparser::parser::ParserOptions; use sqlparser::tokenizer::Token; #[test] @@ -363,7 +363,7 @@ .unwrap_err(); assert_eq!( err, - ParserError::ParserError( + parser_error( "Expected: one of ROLLBACK or ABORT or FAIL or IGNORE or REPLACE, found: BOH" .to_string() ) @@ -625,9 +625,7 @@ for sql in ["SELECT 1 REGEXP", "SELECT 1 MATCH"] { assert_eq!( sqlite().parse_sql_statements(sql), - Err(ParserError::ParserError( - "Expected: an expression, found: EOF".to_string() - )) + Err(parser_error("Expected: an expression, found: EOF")) ); } } @@ -649,9 +647,7 @@ // Should return an error, not panic assert_eq!( sqlite().parse_sql_statements("SELECT 1 GLOB"), - Err(ParserError::ParserError( - "Expected: an expression, found: EOF".to_string() - )) + Err(parser_error("Expected: an expression, found: EOF")) ); } @@ -990,7 +986,7 @@ // `=` still works sqlite().verified_stmt("UPDATE t SET a = 1"); // Other dialects reject `==` in SET - let res = ParserError::ParserError("Expected: =, found: ==".to_string()); + let res = parser_error("Expected: =, found: =="); assert_eq!( all_dialects_except(|d| d.supports_double_eq_assignment()) .parse_sql_statements("UPDATE t SET a == 1") @@ -1007,18 +1003,16 @@ sqlite().verified_stmt("SELECT 𒀀"); sqlite().verified_stmt("SELECT 𒀀𒀁"); // U+DFFFE is not alphabetic, so GenericDialect rejects it as an identifier. + let err = + sqlparser::parser::Parser::parse_sql(&GenericDialect {}, "SELECT \u{DFFFE}").unwrap_err(); assert_eq!( - sqlparser::parser::Parser::parse_sql(&GenericDialect {}, "SELECT \u{DFFFE}"), - Err(ParserError::ParserError( - "Expected: an expression, found: \u{DFFFE} at Line: 1, Column: 8".to_string() - )) + err.to_string(), + "sql parser error: Expected: an expression, found: \u{DFFFE} at Line: 1, Column: 8" ); // SQLite rejects U+007F as an unrecognized token. assert_eq!( sqlite().parse_sql_statements("SELECT \u{007f}"), - Err(ParserError::ParserError( - "Expected: an expression, found: \u{7f}".to_string() - )) + Err(parser_error("Expected: an expression, found: \u{7f}")) ); } @@ -1030,12 +1024,11 @@ r#"CREATE TABLE '""' ('id' INT UNSIGNED NOT NULL, 'name' TEXT NOT NULL, 'zip' INT UNSIGNED NULL)"#, ); // Generic dialect does not support this + let err = sqlparser::parser::Parser::parse_sql(&GenericDialect {}, "CREATE TABLE t ('a')") + .unwrap_err(); assert_eq!( - sqlparser::parser::Parser::parse_sql(&GenericDialect {}, "CREATE TABLE t ('a')"), - Err(ParserError::ParserError( - "Expected: column name or constraint definition, found: 'a' at Line: 1, Column: 17" - .to_string() - )) + err.to_string(), + "sql parser error: Expected: column name or constraint definition, found: 'a' at Line: 1, Column: 17" ); } @@ -1050,7 +1043,7 @@ TestedDialects::new(vec![Box::new(GenericDialect {})]) .parse_sql_statements("SELECT CAST(a AS)") .unwrap_err(), - ParserError::ParserError("Expected: a data type name, found: )".to_string()) + parser_error("Expected: a data type name, found: )") ); } @@ -1136,9 +1129,7 @@ assert_eq!( all_dialects_where(|d| !d.supports_parens_around_table_factor()) .parse_sql_statements("SELECT * FROM (t)"), - Err(ParserError::ParserError( - "Expected: joined table, found: )".to_string() - )) + Err(parser_error("Expected: joined table, found: )")) ); }
diff --git a/tests/sqlparser_teradata.rs b/tests/sqlparser_teradata.rs index 86e7f06..838f938 100644 --- a/tests/sqlparser_teradata.rs +++ b/tests/sqlparser_teradata.rs
@@ -18,9 +18,8 @@ //! Test SQL syntax, specific to [sqlparser::dialect::TeradataDialect]. use sqlparser::dialect::{Dialect, GenericDialect, TeradataDialect}; -use sqlparser::parser::ParserError; use sqlparser::test_utils::all_dialects_where; -use test_utils::TestedDialects; +use test_utils::{parser_error, TestedDialects}; mod test_utils; @@ -120,8 +119,6 @@ all_dialects_where(|d| !d.supports_leading_comma_before_table_options()); assert_eq!( unsupported_dialects.parse_sql_statements("CREATE TABLE foo, FALLBACK (id INT)"), - Err(ParserError::ParserError( - "Expected: end of statement, found: ,".to_string() - )) + Err(parser_error("Expected: end of statement, found: ,")) ); }