Support identifiers beginning with digits in MySQL (#856)

This commit is contained in:
AviRaboah 2023-04-26 16:27:04 +03:00 committed by GitHub
parent 6b2b3f1f6c
commit 3e92ad349f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 3 deletions

View file

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

View file

@ -673,10 +673,10 @@ impl<'a> Tokenizer<'a> {
return Ok(Some(Token::Period)); return Ok(Some(Token::Period));
} }
let mut exponent_part = String::new();
// Parse exponent as number // Parse exponent as number
if chars.peek() == Some(&'e') || chars.peek() == Some(&'E') { if chars.peek() == Some(&'e') || chars.peek() == Some(&'E') {
let mut char_clone = chars.peekable.clone(); let mut char_clone = chars.peekable.clone();
let mut exponent_part = String::new();
exponent_part.push(char_clone.next().unwrap()); exponent_part.push(char_clone.next().unwrap());
// Optional sign // 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') { let long = if chars.peek() == Some(&'L') {
chars.next(); chars.next();
true true

View file

@ -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] #[test]
fn parse_update_with_joins() { 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'"; let sql = "UPDATE orders AS o JOIN customers AS c ON o.customer_id = c.id SET o.completed = true WHERE c.firstname = 'Peter'";