Skip to content
Draft
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
52 changes: 42 additions & 10 deletions src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1372,12 +1372,24 @@ impl<'a> Tokenizer<'a> {
ch.is_ascii_digit() || is_number_separator(ch, next_ch)
});

if self.dialect.supports_numeric_literal_underscores()
&& chars.peek() == Some(&'_')
{
return self.tokenizer_error(chars.location(), "Unexpected character '_'");
}

// match binary literal that starts with 0x
if s == "0" && chars.peek() == Some(&'x') {
chars.next();
let s2 = peeking_next_take_while(chars, |ch, next_ch| {
ch.is_ascii_hexdigit() || is_number_separator(ch, next_ch)
});
if self.dialect.supports_numeric_literal_underscores()
&& chars.peek() == Some(&'_')
{
return self
.tokenizer_error(chars.location(), "Unexpected character '_'");
}
return Ok(Some(Token::HexStringLiteral(s2)));
}

Expand All @@ -1398,11 +1410,24 @@ impl<'a> Tokenizer<'a> {
}
}

if s != "."
&& self.dialect.supports_numeric_literal_underscores()
&& chars.peek() == Some(&'_')
{
return self.tokenizer_error(chars.location(), "Unexpected character '_'");
}

// Consume fractional digits.
s += &peeking_next_take_while(chars, |ch, next_ch| {
ch.is_ascii_digit() || is_number_separator(ch, next_ch)
});

if self.dialect.supports_numeric_literal_underscores()
&& chars.peek() == Some(&'_')
{
return self.tokenizer_error(chars.location(), "Unexpected character '_'");
}

// No fraction -> Token::Period
if s == "." {
return Ok(Some(Token::Period));
Expand Down Expand Up @@ -2756,8 +2781,11 @@ mod tests {
];
compare(expected, tokens);

all_dialects_where(|dialect| dialect.supports_numeric_literal_underscores()).tokenizes_to(
"SELECT 10_000, _10_000, 10_00_, 10___0, 1_000.123, 1_000.123_456",
let numeric_underscore_dialects =
all_dialects_where(|dialect| dialect.supports_numeric_literal_underscores());

numeric_underscore_dialects.tokenizes_to(
"SELECT 10_000, _10_000, 1_000.123, 1_000.123_456",
vec![
Token::make_keyword("SELECT"),
Token::Whitespace(Whitespace::Space),
Expand All @@ -2767,20 +2795,24 @@ mod tests {
Token::make_word("_10_000", None), // leading underscore tokenizes as a word (parsed as column identifier)
Token::Comma,
Token::Whitespace(Whitespace::Space),
Token::Number("10_00".to_string(), false),
Token::make_word("_", None), // trailing underscores tokenizes as a word (syntax error in some dialects)
Token::Comma,
Token::Whitespace(Whitespace::Space),
Token::Number("10".to_string(), false),
Token::make_word("___0", None), // multiple underscores tokenizes as a word (syntax error in some dialects)
Token::Comma,
Token::Whitespace(Whitespace::Space),
Token::Number("1_000.123".to_string(), false), // with decimal digits
Token::Comma,
Token::Whitespace(Whitespace::Space),
Token::Number("1_000.123_456".to_string(), false), // with an underscore in the decimal digits
],
);

for dialect in &numeric_underscore_dialects.dialects {
for sql in [
"SELECT 10_00_",
"SELECT 10___0",
"SELECT 1_000.123_",
"SELECT 1._000",
] {
let err = Tokenizer::new(&**dialect, sql).tokenize().unwrap_err();
assert_eq!("Unexpected character '_'", err.message);
}
}
}

#[test]
Expand Down
12 changes: 12 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ fn parse_numeric_literal_underscore() {
select.projection,
vec![UnnamedExpr(Expr::Value(number("10_000").with_empty_span()))]
);

for (sql, column) in [
("SELECT 10__00", 10),
("SELECT 10_00_", 13),
("SELECT 1._000", 10),
] {
let err = dialects.parse_sql_statements(sql).unwrap_err();
assert_eq!(
format!("sql parser error: Unexpected character '_' at Line: 1, Column: {column}"),
err.to_string()
);
}
}

#[test]
Expand Down