diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs index 82cbc536..4a2ff016 100644 --- a/src/dialect/mysql.rs +++ b/src/dialect/mysql.rs @@ -18,8 +18,8 @@ pub struct MySqlDialect {} impl Dialect for MySqlDialect { fn is_identifier_start(&self, ch: char) -> bool { // See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html. - // We don't yet support identifiers beginning with numbers, as that - // makes it hard to distinguish numeric literals. + // Identifiers which begin with a digit are recognized while tokenizing numbers, + // so they can be distinguished from exponent numeric literals. ch.is_alphabetic() || ch == '_' || ch == '$' diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 010566d7..a550c4f5 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -673,10 +673,10 @@ impl<'a> Tokenizer<'a> { return Ok(Some(Token::Period)); } + let mut exponent_part = String::new(); // Parse exponent as number if chars.peek() == Some(&'e') || chars.peek() == Some(&'E') { let mut char_clone = chars.peekable.clone(); - let mut exponent_part = String::new(); exponent_part.push(char_clone.next().unwrap()); // Optional sign @@ -703,6 +703,18 @@ impl<'a> Tokenizer<'a> { } } + // mysql dialect supports identifiers that start with a numeric prefix, + // as long as they aren't an exponent number. + if dialect_of!(self is MySqlDialect) && exponent_part.is_empty() { + let word = + peeking_take_while(chars, |ch| self.dialect.is_identifier_part(ch)); + + if !word.is_empty() { + s += word.as_str(); + return Ok(Some(Token::make_word(s.as_str(), None))); + } + } + let long = if chars.peek() == Some(&'L') { chars.next(); true diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 0133cc9d..2b312dc5 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -849,6 +849,106 @@ fn parse_insert_with_on_duplicate_update() { } } +#[test] +fn parse_select_with_numeric_prefix_column_name() { + let sql = "SELECT 123col_$@123abc FROM \"table\""; + match mysql().verified_stmt(sql) { + Statement::Query(q) => { + assert_eq!( + q.body, + Box::new(SetExpr::Select(Box::new(Select { + distinct: false, + top: None, + projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new( + "123col_$@123abc" + )))], + into: None, + from: vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident::with_quote('"', "table")]), + alias: None, + args: None, + with_hints: vec![], + }, + joins: vec![] + }], + lateral_views: vec![], + selection: None, + group_by: vec![], + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + qualify: None, + }))) + ); + } + _ => unreachable!(), + } +} + +#[cfg(not(feature = "bigdecimal"))] +#[test] +fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() { + let sql = "SELECT 123e4, 123col_$@123abc FROM \"table\""; + match mysql().verified_stmt(sql) { + Statement::Query(q) => { + assert_eq!( + q.body, + Box::new(SetExpr::Select(Box::new(Select { + distinct: false, + top: None, + projection: vec![ + SelectItem::UnnamedExpr(Expr::Value(Value::Number( + "123e4".to_string(), + false + ))), + SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("123col_$@123abc"))) + ], + into: None, + from: vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident::with_quote('"', "table")]), + alias: None, + args: None, + with_hints: vec![], + }, + joins: vec![] + }], + lateral_views: vec![], + selection: None, + group_by: vec![], + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + qualify: None, + }))) + ); + } + _ => unreachable!(), + } +} + +#[test] +fn parse_insert_with_numeric_prefix_column_name() { + let sql = "INSERT INTO s1.t1 (123col_$@length123) VALUES (67.654)"; + match mysql().verified_stmt(sql) { + Statement::Insert { + table_name, + columns, + .. + } => { + assert_eq!( + ObjectName(vec![Ident::new("s1"), Ident::new("t1")]), + table_name + ); + assert_eq!(vec![Ident::new("123col_$@length123")], columns); + } + _ => unreachable!(), + } +} + #[test] fn parse_update_with_joins() { let sql = "UPDATE orders AS o JOIN customers AS c ON o.customer_id = c.id SET o.completed = true WHERE c.firstname = 'Peter'";