@@ -1191,6 +1191,22 @@ impl<'a> Tokenizer<'a> {
1191
1191
}
1192
1192
// numbers and period
1193
1193
'0' ..='9' | '.' => {
1194
+ // special case where if ._ is encountered after a word then that word
1195
+ // is a table and the _ is the start of the col name.
1196
+ // if the prev token is not a word, then this is not a valid sql
1197
+ // word or number.
1198
+ if ch == '.' && chars. peekable . clone ( ) . nth ( 1 ) == Some ( '_' ) {
1199
+ if let Some ( Token :: Word ( _) ) = prev_token {
1200
+ chars. next ( ) ;
1201
+ return Ok ( Some ( Token :: Period ) ) ;
1202
+ }
1203
+
1204
+ return self . tokenizer_error (
1205
+ chars. location ( ) ,
1206
+ "Unexpected character '_'" . to_string ( ) ,
1207
+ ) ;
1208
+ }
1209
+
1194
1210
// Some dialects support underscore as number separator
1195
1211
// There can only be one at a time and it must be followed by another digit
1196
1212
let is_number_separator = |ch : char , next_char : Option < char > | {
@@ -4018,4 +4034,40 @@ mod tests {
4018
4034
] ,
4019
4035
) ;
4020
4036
}
4037
+
4038
+ #[ test]
4039
+ fn tokenize_period_underscore ( ) {
4040
+ let sql = String :: from ( "SELECT table._col" ) ;
4041
+ // a dialect that supports underscores in numeric literals
4042
+ let dialect = PostgreSqlDialect { } ;
4043
+ let tokens = Tokenizer :: new ( & dialect, & sql) . tokenize ( ) . unwrap ( ) ;
4044
+
4045
+ let expected = vec ! [
4046
+ Token :: make_keyword( "SELECT" ) ,
4047
+ Token :: Whitespace ( Whitespace :: Space ) ,
4048
+ Token :: Word ( Word {
4049
+ value: "table" . to_string( ) ,
4050
+ quote_style: None ,
4051
+ keyword: Keyword :: TABLE ,
4052
+ } ) ,
4053
+ Token :: Period ,
4054
+ Token :: Word ( Word {
4055
+ value: "_col" . to_string( ) ,
4056
+ quote_style: None ,
4057
+ keyword: Keyword :: NoKeyword ,
4058
+ } ) ,
4059
+ ] ;
4060
+
4061
+ compare ( expected, tokens) ;
4062
+
4063
+ let sql = String :: from ( "SELECT ._123" ) ;
4064
+ if let Ok ( tokens) = Tokenizer :: new ( & dialect, & sql) . tokenize ( ) {
4065
+ panic ! ( "Tokenizer should have failed on {sql}, but it succeeded with {tokens:?}" ) ;
4066
+ }
4067
+
4068
+ let sql = String :: from ( "SELECT ._abc" ) ;
4069
+ if let Ok ( tokens) = Tokenizer :: new ( & dialect, & sql) . tokenize ( ) {
4070
+ panic ! ( "Tokenizer should have failed on {sql}, but it succeeded with {tokens:?}" ) ;
4071
+ }
4072
+ }
4021
4073
}
0 commit comments