@@ -1119,38 +1119,49 @@ impl<'a> Tokenizer<'a> {
1119
1119
1120
1120
if let Some ( '$' ) = chars. peek ( ) {
1121
1121
chars. next ( ) ;
1122
- s. push_str ( & peeking_take_while ( chars, |ch| ch != '$' ) ) ;
1123
1122
1124
- match chars. peek ( ) {
1125
- Some ( '$' ) => {
1126
- chars. next ( ) ;
1127
- for c in value. chars ( ) {
1128
- let next_char = chars. next ( ) ;
1129
- if Some ( c) != next_char {
1130
- return self . tokenizer_error (
1131
- chars. location ( ) ,
1132
- format ! (
1133
- "Unterminated dollar-quoted string at or near \" {value}\" "
1134
- ) ,
1135
- ) ;
1123
+ ' searching_for_end: loop {
1124
+ s. push_str ( & peeking_take_while ( chars, |ch| ch != '$' ) ) ;
1125
+ match chars. peek ( ) {
1126
+ Some ( '$' ) => {
1127
+ chars. next ( ) ;
1128
+ let mut maybe_s = String :: from ( "$" ) ;
1129
+ for c in value. chars ( ) {
1130
+ if let Some ( next_char) = chars. next ( ) {
1131
+ maybe_s. push ( next_char) ;
1132
+ if next_char != c {
1133
+ // This doesn't match the dollar quote delimiter so this
1134
+ // is not the end of the string.
1135
+ s. push_str ( & maybe_s) ;
1136
+ continue ' searching_for_end;
1137
+ }
1138
+ } else {
1139
+ return self . tokenizer_error (
1140
+ chars. location ( ) ,
1141
+ "Unterminated dollar-quoted, expected $" ,
1142
+ ) ;
1143
+ }
1144
+ }
1145
+ if chars. peek ( ) == Some ( & '$' ) {
1146
+ chars. next ( ) ;
1147
+ maybe_s. push ( '$' ) ;
1148
+ // maybe_s matches the end delimiter
1149
+ break ' searching_for_end;
1150
+ } else {
1151
+ // This also doesn't match the dollar quote delimiter as there are
1152
+ // more characters before the second dollar so this is not the end
1153
+ // of the string.
1154
+ s. push_str ( & maybe_s) ;
1155
+ continue ' searching_for_end;
1136
1156
}
1137
1157
}
1138
-
1139
- if let Some ( '$' ) = chars. peek ( ) {
1140
- chars. next ( ) ;
1141
- } else {
1158
+ _ => {
1142
1159
return self . tokenizer_error (
1143
1160
chars. location ( ) ,
1144
- "Unterminated dollar-quoted string , expected $" ,
1145
- ) ;
1161
+ "Unterminated dollar-quoted, expected $" ,
1162
+ )
1146
1163
}
1147
1164
}
1148
- _ => {
1149
- return self . tokenizer_error (
1150
- chars. location ( ) ,
1151
- "Unterminated dollar-quoted, expected $" ,
1152
- ) ;
1153
- }
1154
1165
}
1155
1166
} else {
1156
1167
return Ok ( Token :: Placeholder ( String :: from ( "$" ) + & value) ) ;
@@ -1906,6 +1917,75 @@ mod tests {
1906
1917
compare ( expected, tokens) ;
1907
1918
}
1908
1919
1920
+ #[ test]
1921
+ fn tokenize_dollar_quoted_string_tagged ( ) {
1922
+ let sql = String :: from (
1923
+ "SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$tag$" ,
1924
+ ) ;
1925
+ let dialect = GenericDialect { } ;
1926
+ let tokens = Tokenizer :: new ( & dialect, & sql) . tokenize ( ) . unwrap ( ) ;
1927
+ let expected = vec ! [
1928
+ Token :: make_keyword( "SELECT" ) ,
1929
+ Token :: Whitespace ( Whitespace :: Space ) ,
1930
+ Token :: DollarQuotedString ( DollarQuotedString {
1931
+ value: "dollar '$' quoted strings have $tags like this$ or like this $$" . into( ) ,
1932
+ tag: Some ( "tag" . into( ) ) ,
1933
+ } ) ,
1934
+ ] ;
1935
+ compare ( expected, tokens) ;
1936
+ }
1937
+
1938
+ #[ test]
1939
+ fn tokenize_dollar_quoted_string_tagged_unterminated ( ) {
1940
+ let sql = String :: from ( "SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$different tag$" ) ;
1941
+ let dialect = GenericDialect { } ;
1942
+ assert_eq ! (
1943
+ Tokenizer :: new( & dialect, & sql) . tokenize( ) ,
1944
+ Err ( TokenizerError {
1945
+ message: "Unterminated dollar-quoted, expected $" . into( ) ,
1946
+ location: Location {
1947
+ line: 1 ,
1948
+ column: 91
1949
+ }
1950
+ } )
1951
+ ) ;
1952
+ }
1953
+
1954
+ #[ test]
1955
+ fn tokenize_dollar_quoted_string_untagged ( ) {
1956
+ let sql =
1957
+ String :: from ( "SELECT $$within dollar '$' quoted strings have $tags like this$ $$" ) ;
1958
+ let dialect = GenericDialect { } ;
1959
+ let tokens = Tokenizer :: new ( & dialect, & sql) . tokenize ( ) . unwrap ( ) ;
1960
+ let expected = vec ! [
1961
+ Token :: make_keyword( "SELECT" ) ,
1962
+ Token :: Whitespace ( Whitespace :: Space ) ,
1963
+ Token :: DollarQuotedString ( DollarQuotedString {
1964
+ value: "within dollar '$' quoted strings have $tags like this$ " . into( ) ,
1965
+ tag: None ,
1966
+ } ) ,
1967
+ ] ;
1968
+ compare ( expected, tokens) ;
1969
+ }
1970
+
1971
+ #[ test]
1972
+ fn tokenize_dollar_quoted_string_untagged_unterminated ( ) {
1973
+ let sql = String :: from (
1974
+ "SELECT $$dollar '$' quoted strings have $tags like this$ or like this $different tag$" ,
1975
+ ) ;
1976
+ let dialect = GenericDialect { } ;
1977
+ assert_eq ! (
1978
+ Tokenizer :: new( & dialect, & sql) . tokenize( ) ,
1979
+ Err ( TokenizerError {
1980
+ message: "Unterminated dollar-quoted string" . into( ) ,
1981
+ location: Location {
1982
+ line: 1 ,
1983
+ column: 86
1984
+ }
1985
+ } )
1986
+ ) ;
1987
+ }
1988
+
1909
1989
#[ test]
1910
1990
fn tokenize_right_arrow ( ) {
1911
1991
let sql = String :: from ( "FUNCTION(key=>value)" ) ;
0 commit comments