@@ -2287,17 +2287,21 @@ impl<'a> Tokenizer<'a> {
22872287
22882288 num_consecutive_quotes = 0 ;
22892289
2290- if let Some ( next) = chars. peek ( ) {
2290+ if let Some ( & next) = chars. peek ( ) {
22912291 if !self . unescape
22922292 || ( self . dialect . ignores_wildcard_escapes ( )
2293- && ( * next == '%' || * next == '_' ) )
2293+ && ( next == '%' || next == '_' ) )
22942294 {
22952295 // In no-escape mode, the given query has to be saved completely
22962296 // including backslashes. Similarly, with ignore_like_wildcard_escapes,
22972297 // the backslash is not stripped.
22982298 s. push ( ch) ;
2299- s. push ( * next) ;
2299+ s. push ( next) ;
23002300 chars. next ( ) ; // consume next
2301+ } else if self . dialect . supports_snowflake_string_literal_escapes ( ) {
2302+ let escape_loc = chars. location ( ) ;
2303+ chars. next ( ) ; // consume the escape indicator
2304+ s. push ( self . unescape_snowflake_escape ( next, chars, escape_loc) ?) ;
23012305 } else {
23022306 let n = match next {
23032307 '0' => '\0' ,
@@ -2308,7 +2312,7 @@ impl<'a> Tokenizer<'a> {
23082312 'r' => '\r' ,
23092313 't' => '\t' ,
23102314 'Z' => '\u{1a}' ,
2311- _ => * next,
2315+ _ => next,
23122316 } ;
23132317 s. push ( n) ;
23142318 chars. next ( ) ; // consume next
@@ -2331,6 +2335,174 @@ impl<'a> Tokenizer<'a> {
23312335 self . tokenizer_error ( error_loc, "Unterminated string literal" )
23322336 }
23332337
2338+ /// Decode a single Snowflake backslash escape (the backslash and the
2339+ /// `indicator` char have already been consumed). Octal `\ooo`, hex `\xhh`
2340+ /// and unicode `\uhhhh` each yield a Unicode code point; recognized control
2341+ /// letters map to their control character; every other letter collapses to
2342+ /// itself. Malformed `\x` / `\u` raise a tokenizer error, matching real
2343+ /// Snowflake.
2344+ fn unescape_snowflake_escape (
2345+ & self ,
2346+ indicator : char ,
2347+ chars : & mut State ,
2348+ loc : Location ,
2349+ ) -> Result < char , TokenizerError > {
2350+ match indicator {
2351+ 'x' => {
2352+ let mut digits = String :: new ( ) ;
2353+ while digits. len ( ) < 2 {
2354+ match chars. peek ( ) {
2355+ Some ( c) if c. is_ascii_hexdigit ( ) => {
2356+ if let Some ( c) = chars. next ( ) {
2357+ digits. push ( c) ;
2358+ }
2359+ }
2360+ _ => break ,
2361+ }
2362+ }
2363+ if digits. len ( ) < 2 {
2364+ return self . tokenizer_error (
2365+ loc,
2366+ format ! (
2367+ "Invalid hex escape sequence '\\ x{digits}'; should be exactly 2 digits."
2368+ ) ,
2369+ ) ;
2370+ }
2371+ self . code_point ( & digits, 16 , loc)
2372+ }
2373+ 'u' => self . unescape_unicode ( chars, loc) ,
2374+ // Octal `\ooo`: up to three octal digits, but a third digit is only
2375+ // consumed when the running value stays within a single byte
2376+ // (<= 0xFF); otherwise it is left as a literal character.
2377+ '0' ..='7' => {
2378+ let mut value = indicator. to_digit ( 8 ) . unwrap_or ( 0 ) ;
2379+ // Second octal digit always fits in a byte (max 0o77 = 63).
2380+ if let Some ( d) = chars. peek ( ) . and_then ( |c| c. to_digit ( 8 ) ) {
2381+ value = value * 8 + d;
2382+ chars. next ( ) ;
2383+ // Third digit only when the byte does not overflow.
2384+ if let Some ( d) = chars. peek ( ) . and_then ( |c| c. to_digit ( 8 ) ) {
2385+ if value * 8 + d <= 0xFF {
2386+ value = value * 8 + d;
2387+ chars. next ( ) ;
2388+ }
2389+ }
2390+ }
2391+ char:: from_u32 ( value) . map_or_else (
2392+ || self . tokenizer_error ( loc, "Invalid octal escape sequence." ) ,
2393+ Ok ,
2394+ )
2395+ }
2396+ 'b' => Ok ( '\u{8}' ) ,
2397+ 'f' => Ok ( '\u{c}' ) ,
2398+ 'n' => Ok ( '\n' ) ,
2399+ 'r' => Ok ( '\r' ) ,
2400+ 't' => Ok ( '\t' ) ,
2401+ other => Ok ( other) ,
2402+ }
2403+ }
2404+
2405+ fn code_point ( & self , digits : & str , radix : u32 , loc : Location ) -> Result < char , TokenizerError > {
2406+ u32:: from_str_radix ( digits, radix)
2407+ . ok ( )
2408+ . and_then ( char:: from_u32)
2409+ . map_or_else ( || self . tokenizer_error ( loc, "Invalid escape sequence." ) , Ok )
2410+ }
2411+
2412+ /// Decode a `\uhhhh` escape (the `\u` has already been consumed). A high
2413+ /// surrogate must be followed by a `\uhhhh` low surrogate and combines with
2414+ /// it into an astral code point; an unpaired high or low surrogate is an
2415+ /// error, matching real Snowflake.
2416+ fn unescape_unicode ( & self , chars : & mut State , loc : Location ) -> Result < char , TokenizerError > {
2417+ let value = self . read_hex4 ( chars, loc) ?;
2418+ if ( 0xD800 ..=0xDBFF ) . contains ( & value) {
2419+ return match self . read_low_surrogate ( chars) {
2420+ Some ( low) => {
2421+ let cp = 0x1_0000 + ( ( value - 0xD800 ) << 10 ) + ( low - 0xDC00 ) ;
2422+ char:: from_u32 ( cp)
2423+ . map_or_else ( || self . tokenizer_error ( loc, "Invalid escape sequence." ) , Ok )
2424+ }
2425+ None => self . tokenizer_error (
2426+ loc,
2427+ format ! (
2428+ "Invalid Unicode string literal; high surrogate '\\ u{value:04X}' \
2429+ must be followed by a low surrogate ('\\ uDC00'-'\\ uDFFF')."
2430+ ) ,
2431+ ) ,
2432+ } ;
2433+ }
2434+ if ( 0xDC00 ..=0xDFFF ) . contains ( & value) {
2435+ return self . tokenizer_error (
2436+ loc,
2437+ format ! (
2438+ "Invalid Unicode string literal; low surrogate '\\ u{value:04X}' \
2439+ must be preceded by a high surrogate ('\\ uD800'-'\\ uDBFF')."
2440+ ) ,
2441+ ) ;
2442+ }
2443+ char:: from_u32 ( value)
2444+ . map_or_else ( || self . tokenizer_error ( loc, "Invalid escape sequence." ) , Ok )
2445+ }
2446+
2447+ /// Read exactly four hex digits as a `u32`, erroring like Snowflake when
2448+ /// fewer than four are present.
2449+ fn read_hex4 ( & self , chars : & mut State , loc : Location ) -> Result < u32 , TokenizerError > {
2450+ let mut digits = String :: new ( ) ;
2451+ while digits. len ( ) < 4 {
2452+ match chars. peek ( ) {
2453+ Some ( c) if c. is_ascii_hexdigit ( ) => {
2454+ if let Some ( c) = chars. next ( ) {
2455+ digits. push ( c) ;
2456+ }
2457+ }
2458+ _ => break ,
2459+ }
2460+ }
2461+ if digits. len ( ) < 4 {
2462+ return self . tokenizer_error (
2463+ loc,
2464+ format ! (
2465+ "Invalid unicode escape sequence '\\ u{digits}'; should be exactly 4 digits."
2466+ ) ,
2467+ ) ;
2468+ }
2469+ u32:: from_str_radix ( & digits, 16 ) . map_or_else (
2470+ |_| self . tokenizer_error ( loc, "Invalid unicode escape sequence." ) ,
2471+ Ok ,
2472+ )
2473+ }
2474+
2475+ /// Consume a trailing `\uhhhh` low surrogate, returning its value when
2476+ /// present and in range. Consumes greedily on the error path — the caller
2477+ /// aborts the whole token, so partial consumption is irrelevant.
2478+ fn read_low_surrogate ( & self , chars : & mut State ) -> Option < u32 > {
2479+ if chars. peek ( ) != Some ( & '\\' ) {
2480+ return None ;
2481+ }
2482+ chars. next ( ) ;
2483+ if chars. peek ( ) != Some ( & 'u' ) {
2484+ return None ;
2485+ }
2486+ chars. next ( ) ;
2487+ let mut digits = String :: new ( ) ;
2488+ while digits. len ( ) < 4 {
2489+ match chars. peek ( ) {
2490+ Some ( c) if c. is_ascii_hexdigit ( ) => {
2491+ if let Some ( c) = chars. next ( ) {
2492+ digits. push ( c) ;
2493+ }
2494+ }
2495+ _ => break ,
2496+ }
2497+ }
2498+ if digits. len ( ) < 4 {
2499+ return None ;
2500+ }
2501+ u32:: from_str_radix ( & digits, 16 )
2502+ . ok ( )
2503+ . filter ( |low| ( 0xDC00 ..=0xDFFF ) . contains ( low) )
2504+ }
2505+
23342506 fn tokenize_multiline_comment (
23352507 & self ,
23362508 chars : & mut State ,
@@ -3878,10 +4050,13 @@ mod tests {
38784050 ( r#"'%a\'%b'"# , r#"%a\'%b"# , r#"%a'%b"# ) ,
38794051 ( r#"'a\'\'b\'c\'d'"# , r#"a\'\'b\'c\'d"# , r#"a''b'c'd"# ) ,
38804052 ( r#"'\\'"# , r#"\\"# , r#"\"# ) ,
4053+ // Snowflake keeps `\0` as NUL, maps the control-letter escapes, and
4054+ // collapses unrecognized letters (`\a` -> a, `\Z` -> Z) to the bare
4055+ // letter rather than a control character.
38814056 (
38824057 r#"'\0\a\b\f\n\r\t\Z'"# ,
38834058 r#"\0\a\b\f\n\r\t\Z"# ,
3884- "\0 \u{7} \u{ 8}\u{c} \n \r \t \u{1a} " ,
4059+ "\0 a \u{8} \u{c} \n \r \t Z " ,
38854060 ) ,
38864061 ( r#"'\"'"# , r#"\""# , "\" " ) ,
38874062 ( r#"'\\a\\b\'c'"# , r#"\\a\\b\'c"# , r#"\a\b'c"# ) ,
@@ -3890,6 +4065,20 @@ mod tests {
38904065 ( r#"'\q'"# , r#"\q"# , r#"q"# ) ,
38914066 ( r#"'\%\_'"# , r#"\%\_"# , r#"%_"# ) ,
38924067 ( r#"'\\%\\_'"# , r#"\\%\\_"# , r#"\%\_"# ) ,
4068+ // Hex `\xhh` and unicode `\uhhhh` yield a code point; octal `\ooo`
4069+ // takes a third digit only when the byte does not overflow.
4070+ ( r#"'\x41'"# , r#"\x41"# , "A" ) ,
4071+ ( r#"'\u0041'"# , r#"\u0041"# , "A" ) ,
4072+ ( r#"'\u00e9'"# , r#"\u00e9"# , "\u{e9} " ) ,
4073+ ( r#"'\ud83d\ude00'"# , r#"\ud83d\ude00"# , "\u{1f600} " ) ,
4074+ ( r#"'\xff'"# , r#"\xff"# , "\u{ff} " ) ,
4075+ ( r#"'A'"# , r#"A"# , "A" ) ,
4076+ ( r#"'é'"# , r#"é"# , "\u{e9} " ) ,
4077+ ( r#"'ꯍ'"# , r#"ꯍ"# , "\u{abcd} " ) ,
4078+ ( r#"'\101'"# , r#"\101"# , "A" ) ,
4079+ ( r#"'\1012'"# , r#"\1012"# , "A2" ) ,
4080+ ( r#"'\777'"# , r#"\777"# , "\u{3f} 7" ) ,
4081+ ( r#"'\400'"# , r#"\400"# , "\u{20} 0" ) ,
38934082 ] {
38944083 let tokens = Tokenizer :: new ( & dialect, sql)
38954084 . with_unescape ( false )
0 commit comments