Skip to content

Commit ef46ee4

Browse files
committed
Support identifiers beginning with digits in MySQL
1 parent 6b2b3f1 commit ef46ee4

File tree

3 files changed

+72
-3
lines changed

3 files changed

+72
-3
lines changed

src/dialect/mysql.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ pub struct MySqlDialect {}
1818
impl Dialect for MySqlDialect {
1919
fn is_identifier_start(&self, ch: char) -> bool {
2020
// See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html.
21-
// We don't yet support identifiers beginning with numbers, as that
22-
// makes it hard to distinguish numeric literals.
21+
// Identifiers which begin with a digit are recognized while tokenizing numbers,
22+
// so they can be distinguished from exponent numeric literals.
2323
ch.is_alphabetic()
2424
|| ch == '_'
2525
|| ch == '$'

src/tokenizer.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,10 +673,10 @@ impl<'a> Tokenizer<'a> {
673673
return Ok(Some(Token::Period));
674674
}
675675

676+
let mut exponent_part = String::new();
676677
// Parse exponent as number
677678
if chars.peek() == Some(&'e') || chars.peek() == Some(&'E') {
678679
let mut char_clone = chars.peekable.clone();
679-
let mut exponent_part = String::new();
680680
exponent_part.push(char_clone.next().unwrap());
681681

682682
// Optional sign
@@ -703,6 +703,18 @@ impl<'a> Tokenizer<'a> {
703703
}
704704
}
705705

706+
// mysql dialect supports identifiers that start with a numeric prefix,
707+
// as long as they aren't an exponent number.
708+
if dialect_of!(self is MySqlDialect) && exponent_part.is_empty() {
709+
let word =
710+
peeking_take_while(chars, |ch| self.dialect.is_identifier_part(ch));
711+
712+
if !word.is_empty() {
713+
s += word.as_str();
714+
return Ok(Some(Token::make_word(s.as_str(), None)));
715+
}
716+
}
717+
706718
let long = if chars.peek() == Some(&'L') {
707719
chars.next();
708720
true

tests/sqlparser_mysql.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,63 @@ fn parse_insert_with_on_duplicate_update() {
849849
}
850850
}
851851

852+
#[test]
853+
fn parse_select_with_numeric_prefix_column_name() {
854+
let sql = "SELECT 123col_$@123abc FROM \"table\"";
855+
match mysql().verified_stmt(sql) {
856+
Statement::Query(q) => {
857+
assert_eq!(
858+
q.body,
859+
Box::new(SetExpr::Select(Box::new(Select {
860+
distinct: false,
861+
top: None,
862+
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(
863+
"123col_$@123abc"
864+
)))],
865+
into: None,
866+
from: vec![TableWithJoins {
867+
relation: TableFactor::Table {
868+
name: ObjectName(vec![Ident::with_quote('"', "table")]),
869+
alias: None,
870+
args: None,
871+
with_hints: vec![],
872+
},
873+
joins: vec![]
874+
}],
875+
lateral_views: vec![],
876+
selection: None,
877+
group_by: vec![],
878+
cluster_by: vec![],
879+
distribute_by: vec![],
880+
sort_by: vec![],
881+
having: None,
882+
qualify: None,
883+
})))
884+
);
885+
}
886+
_ => unreachable!(),
887+
}
888+
}
889+
890+
#[test]
891+
fn parse_insert_with_numeric_prefix_column_name() {
892+
let sql = "INSERT INTO s1.t1 (123col_$@length123) VALUES (67.654)";
893+
match mysql().verified_stmt(sql) {
894+
Statement::Insert {
895+
table_name,
896+
columns,
897+
..
898+
} => {
899+
assert_eq!(
900+
ObjectName(vec![Ident::new("s1"), Ident::new("t1")]),
901+
table_name
902+
);
903+
assert_eq!(vec![Ident::new("123col_$@length123")], columns);
904+
}
905+
_ => unreachable!(),
906+
}
907+
}
908+
852909
#[test]
853910
fn parse_update_with_joins() {
854911
let sql = "UPDATE orders AS o JOIN customers AS c ON o.customer_id = c.id SET o.completed = true WHERE c.firstname = 'Peter'";

0 commit comments

Comments
 (0)