@@ -1566,46 +1566,33 @@ impl<'a> Tokenizer<'a> {
1566
1566
if matches ! ( chars. peek( ) , Some ( '$' ) ) && !self . dialect . supports_dollar_placeholder ( ) {
1567
1567
chars. next ( ) ;
1568
1568
1569
- ' searching_for_end: loop {
1570
- s. push_str ( & peeking_take_while ( chars, |ch| ch != '$' ) ) ;
1571
- match chars. peek ( ) {
1572
- Some ( '$' ) => {
1573
- chars. next ( ) ;
1574
- let mut maybe_s = String :: from ( "$" ) ;
1575
- for c in value. chars ( ) {
1576
- if let Some ( next_char) = chars. next ( ) {
1577
- maybe_s. push ( next_char) ;
1578
- if next_char != c {
1579
- // This doesn't match the dollar quote delimiter so this
1580
- // is not the end of the string.
1581
- s. push_str ( & maybe_s) ;
1582
- continue ' searching_for_end;
1583
- }
1584
- } else {
1585
- return self . tokenizer_error (
1586
- chars. location ( ) ,
1587
- "Unterminated dollar-quoted, expected $" ,
1588
- ) ;
1569
+ let mut temp = String :: new ( ) ;
1570
+ let end_delimiter = format ! ( "${}$" , value) ;
1571
+
1572
+ loop {
1573
+ match chars. next ( ) {
1574
+ Some ( ch) => {
1575
+ temp. push ( ch) ;
1576
+
1577
+ if temp. ends_with ( & end_delimiter) {
1578
+ if let Some ( temp) = temp. strip_suffix ( & end_delimiter) {
1579
+ s. push_str ( temp) ;
1589
1580
}
1590
- }
1591
- if chars. peek ( ) == Some ( & '$' ) {
1592
- chars. next ( ) ;
1593
- maybe_s. push ( '$' ) ;
1594
- // maybe_s matches the end delimiter
1595
- break ' searching_for_end;
1596
- } else {
1597
- // This also doesn't match the dollar quote delimiter as there are
1598
- // more characters before the second dollar so this is not the end
1599
- // of the string.
1600
- s. push_str ( & maybe_s) ;
1601
- continue ' searching_for_end;
1581
+ break ;
1602
1582
}
1603
1583
}
1604
- _ => {
1584
+ None => {
1585
+ if temp. ends_with ( & end_delimiter) {
1586
+ if let Some ( temp) = temp. strip_suffix ( & end_delimiter) {
1587
+ s. push_str ( temp) ;
1588
+ }
1589
+ break ;
1590
+ }
1591
+
1605
1592
return self . tokenizer_error (
1606
1593
chars. location ( ) ,
1607
1594
"Unterminated dollar-quoted, expected $" ,
1608
- )
1595
+ ) ;
1609
1596
}
1610
1597
}
1611
1598
}
@@ -2569,20 +2556,67 @@ mod tests {
2569
2556
2570
2557
#[ test]
2571
2558
fn tokenize_dollar_quoted_string_tagged ( ) {
2572
- let sql = String :: from (
2573
- "SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$tag$" ,
2574
- ) ;
2575
- let dialect = GenericDialect { } ;
2576
- let tokens = Tokenizer :: new ( & dialect, & sql) . tokenize ( ) . unwrap ( ) ;
2577
- let expected = vec ! [
2578
- Token :: make_keyword( "SELECT" ) ,
2579
- Token :: Whitespace ( Whitespace :: Space ) ,
2580
- Token :: DollarQuotedString ( DollarQuotedString {
2581
- value: "dollar '$' quoted strings have $tags like this$ or like this $$" . into( ) ,
2582
- tag: Some ( "tag" . into( ) ) ,
2583
- } ) ,
2559
+ let test_cases = vec ! [
2560
+ (
2561
+ String :: from( "SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$tag$" ) ,
2562
+ vec![
2563
+ Token :: make_keyword( "SELECT" ) ,
2564
+ Token :: Whitespace ( Whitespace :: Space ) ,
2565
+ Token :: DollarQuotedString ( DollarQuotedString {
2566
+ value: "dollar '$' quoted strings have $tags like this$ or like this $$" . into( ) ,
2567
+ tag: Some ( "tag" . into( ) ) ,
2568
+ } )
2569
+ ]
2570
+ ) ,
2571
+ (
2572
+ String :: from( "SELECT $abc$x$ab$abc$" ) ,
2573
+ vec![
2574
+ Token :: make_keyword( "SELECT" ) ,
2575
+ Token :: Whitespace ( Whitespace :: Space ) ,
2576
+ Token :: DollarQuotedString ( DollarQuotedString {
2577
+ value: "x$ab" . into( ) ,
2578
+ tag: Some ( "abc" . into( ) ) ,
2579
+ } )
2580
+ ]
2581
+ ) ,
2582
+ (
2583
+ String :: from( "SELECT $abc$$abc$" ) ,
2584
+ vec![
2585
+ Token :: make_keyword( "SELECT" ) ,
2586
+ Token :: Whitespace ( Whitespace :: Space ) ,
2587
+ Token :: DollarQuotedString ( DollarQuotedString {
2588
+ value: "" . into( ) ,
2589
+ tag: Some ( "abc" . into( ) ) ,
2590
+ } )
2591
+ ]
2592
+ ) ,
2593
+ (
2594
+ String :: from( "0$abc$$abc$1" ) ,
2595
+ vec![
2596
+ Token :: Number ( "0" . into( ) , false ) ,
2597
+ Token :: DollarQuotedString ( DollarQuotedString {
2598
+ value: "" . into( ) ,
2599
+ tag: Some ( "abc" . into( ) ) ,
2600
+ } ) ,
2601
+ Token :: Number ( "1" . into( ) , false ) ,
2602
+ ]
2603
+ ) ,
2604
+ (
2605
+ String :: from( "$function$abc$q$data$q$$function$" ) ,
2606
+ vec![
2607
+ Token :: DollarQuotedString ( DollarQuotedString {
2608
+ value: "abc$q$data$q$" . into( ) ,
2609
+ tag: Some ( "function" . into( ) ) ,
2610
+ } ) ,
2611
+ ]
2612
+ ) ,
2584
2613
] ;
2585
- compare ( expected, tokens) ;
2614
+
2615
+ let dialect = GenericDialect { } ;
2616
+ for ( sql, expected) in test_cases {
2617
+ let tokens = Tokenizer :: new ( & dialect, & sql) . tokenize ( ) . unwrap ( ) ;
2618
+ compare ( expected, tokens) ;
2619
+ }
2586
2620
}
2587
2621
2588
2622
#[ test]
@@ -2601,6 +2635,22 @@ mod tests {
2601
2635
) ;
2602
2636
}
2603
2637
2638
+ #[ test]
2639
+ fn tokenize_dollar_quoted_string_tagged_unterminated_mirror ( ) {
2640
+ let sql = String :: from ( "SELECT $abc$abc$" ) ;
2641
+ let dialect = GenericDialect { } ;
2642
+ assert_eq ! (
2643
+ Tokenizer :: new( & dialect, & sql) . tokenize( ) ,
2644
+ Err ( TokenizerError {
2645
+ message: "Unterminated dollar-quoted, expected $" . into( ) ,
2646
+ location: Location {
2647
+ line: 1 ,
2648
+ column: 17
2649
+ }
2650
+ } )
2651
+ ) ;
2652
+ }
2653
+
2604
2654
#[ test]
2605
2655
fn tokenize_dollar_placeholder ( ) {
2606
2656
let sql = String :: from ( "SELECT $$, $$ABC$$, $ABC$, $ABC" ) ;
@@ -2625,6 +2675,38 @@ mod tests {
2625
2675
) ;
2626
2676
}
2627
2677
2678
+ #[ test]
2679
+ fn tokenize_nested_dollar_quoted_strings ( ) {
2680
+ let sql = String :: from ( "SELECT $tag$dollar $nested$ string$tag$" ) ;
2681
+ let dialect = GenericDialect { } ;
2682
+ let tokens = Tokenizer :: new ( & dialect, & sql) . tokenize ( ) . unwrap ( ) ;
2683
+ let expected = vec ! [
2684
+ Token :: make_keyword( "SELECT" ) ,
2685
+ Token :: Whitespace ( Whitespace :: Space ) ,
2686
+ Token :: DollarQuotedString ( DollarQuotedString {
2687
+ value: "dollar $nested$ string" . into( ) ,
2688
+ tag: Some ( "tag" . into( ) ) ,
2689
+ } ) ,
2690
+ ] ;
2691
+ compare ( expected, tokens) ;
2692
+ }
2693
+
2694
+ #[ test]
2695
+ fn tokenize_dollar_quoted_string_untagged_empty ( ) {
2696
+ let sql = String :: from ( "SELECT $$$$" ) ;
2697
+ let dialect = GenericDialect { } ;
2698
+ let tokens = Tokenizer :: new ( & dialect, & sql) . tokenize ( ) . unwrap ( ) ;
2699
+ let expected = vec ! [
2700
+ Token :: make_keyword( "SELECT" ) ,
2701
+ Token :: Whitespace ( Whitespace :: Space ) ,
2702
+ Token :: DollarQuotedString ( DollarQuotedString {
2703
+ value: "" . into( ) ,
2704
+ tag: None ,
2705
+ } ) ,
2706
+ ] ;
2707
+ compare ( expected, tokens) ;
2708
+ }
2709
+
2628
2710
#[ test]
2629
2711
fn tokenize_dollar_quoted_string_untagged ( ) {
2630
2712
let sql =
0 commit comments