Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/fuzz_seeds/databricks_2771_long_literal.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT 1L, 1La
5 changes: 5 additions & 0 deletions src/dialect/databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ impl Dialect for DatabricksDialect {
true
}

/// See <https://docs.databricks.com/aws/en/sql/language-manual/data-types/bigint-type>
fn supports_long_suffix_before_numeric_prefix(&self) -> bool {
true
}

fn supports_filter_during_aggregation(&self) -> bool {
true
}
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/hive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ impl Dialect for HiveDialect {
true
}

/// See <https://hive.apache.org/docs/latest/language/languagemanual-types/>
fn supports_long_suffix_before_numeric_prefix(&self) -> bool {
true
}

fn require_interval_qualifier(&self) -> bool {
true
}
Expand Down
7 changes: 7 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 14 additions & 0 deletions src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
25 changes: 25 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")))]
);
}
Loading