diff --git a/fuzz/fuzz_seeds/databricks_2771_long_literal.sql b/fuzz/fuzz_seeds/databricks_2771_long_literal.sql new file mode 100644 index 0000000000..d3c60f2764 --- /dev/null +++ b/fuzz/fuzz_seeds/databricks_2771_long_literal.sql @@ -0,0 +1 @@ +SELECT 1L, 1La diff --git a/src/dialect/databricks.rs b/src/dialect/databricks.rs index d660cec9eb..79fe7bc29b 100644 --- a/src/dialect/databricks.rs +++ b/src/dialect/databricks.rs @@ -72,6 +72,11 @@ impl Dialect for DatabricksDialect { true } + /// See + fn supports_long_suffix_before_numeric_prefix(&self) -> bool { + true + } + fn supports_filter_during_aggregation(&self) -> bool { true } diff --git a/src/dialect/hive.rs b/src/dialect/hive.rs index e80bc75d2b..6b8889aea7 100644 --- a/src/dialect/hive.rs +++ b/src/dialect/hive.rs @@ -78,6 +78,11 @@ impl Dialect for HiveDialect { true } + /// See + fn supports_long_suffix_before_numeric_prefix(&self) -> bool { + true + } + fn require_interval_qualifier(&self) -> bool { true } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 20fe597d54..cf7ef43cdd 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -597,6 +597,13 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if an `L` ending a number, as in `1L`, makes it a long + /// literal even when [`Dialect::supports_numeric_prefix`] would otherwise + /// read `1L` as an identifier. `1La` is still an identifier. + fn supports_long_suffix_before_numeric_prefix(&self) -> bool { + false + } + /// Returns true if a period directly after an identifier starts a tuple /// element access such as `t.1`, instead of the number `.1`. fn supports_tuple_element_access(&self) -> bool { diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 02a54f08ff..4abe4165a8 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -1686,6 +1686,20 @@ impl<'a> Tokenizer<'a> { } } + if self.dialect.supports_long_suffix_before_numeric_prefix() + && chars.peek() == Some(&'L') + { + let mut ahead = chars.peekable.clone(); + ahead.next(); + if !ahead + .peek() + .is_some_and(|ch| self.dialect.is_identifier_part(*ch)) + { + chars.next(); + return Ok(Some(Token::Number(s, true))); + } + } + // If the dialect supports identifiers that start with a numeric prefix, // we need to check if the value is in fact an identifier and must thus // be tokenized as a word. diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 379e37a660..996404acfd 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -22744,3 +22744,28 @@ fn display_create_procedure_parameter_list_propagates_write_errors() { let mut writer = RejectingWriter(" ("); assert_eq!(write!(writer, "{statement}"), Err(fmt::Error)); } + +#[test] +fn parse_long_suffix_before_numeric_prefix() { + let dialects = all_dialects_where(|d| d.supports_long_suffix_before_numeric_prefix()); + assert_eq!( + dialects.verified_only_select("SELECT 1L").projection, + vec![SelectItem::UnnamedExpr(Expr::value(Value::Number( + "1".parse().unwrap(), + true + )))] + ); + assert_eq!( + dialects.verified_only_select("SELECT 1La").projection, + vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("1La")))] + ); + + assert_eq!( + all_dialects_where(|d| { + d.supports_numeric_prefix() && !d.supports_long_suffix_before_numeric_prefix() + }) + .verified_only_select("SELECT 1L") + .projection, + vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("1L")))] + ); +}