@@ -17,6 +17,7 @@ use alloc::collections::BTreeSet;
1717#[cfg(not(feature = "std"))]
1818use alloc::{
1919 boxed::Box,
20+ collections::BTreeMap,
2021 format,
2122 string::{String, ToString},
2223 vec,
@@ -26,6 +27,9 @@ use core::{
2627 fmt::{self, Display},
2728 str::FromStr,
2829};
30+ #[cfg(feature = "std")]
31+ use std::collections::BTreeMap;
32+
2933use helpers::attached_token::AttachedToken;
3034#[cfg(feature = "std")]
3135use std::collections::BTreeSet;
@@ -371,6 +375,12 @@ pub struct Parser<'a> {
371375 /// `<ident>-NOT-<ident>.` ending in a parse error) trigger 2^N exploration
372376 /// because each `-NOT-` segment otherwise re-walks the rest of the chain.
373377 failed_unary_not_positions: BTreeSet<usize>,
378+ /// Cached errors from `parse_prefix` calls that returned `Err`. See
379+ /// [`Parser::parse_prefix`] for the 2^N patterns this guards.
380+ failed_prefix_positions: BTreeMap<usize, ParserError>,
381+ /// Cached errors from the speculative reserved-word prefix arm. See
382+ /// [`Parser::parse_prefix`] for the 2^N patterns this guards.
383+ failed_reserved_word_prefix_positions: BTreeMap<usize, ParserError>,
374384}
375385
376386impl<'a> Parser<'a> {
@@ -398,6 +408,8 @@ impl<'a> Parser<'a> {
398408 recursion_counter: RecursionCounter::new(DEFAULT_REMAINING_DEPTH),
399409 options: ParserOptions::new().with_trailing_commas(dialect.supports_trailing_commas()),
400410 failed_unary_not_positions: BTreeSet::new(),
411+ failed_prefix_positions: BTreeMap::new(),
412+ failed_reserved_word_prefix_positions: BTreeMap::new(),
401413 }
402414 }
403415
@@ -460,6 +472,8 @@ impl<'a> Parser<'a> {
460472 self.tokens = tokens;
461473 self.index = 0;
462474 self.failed_unary_not_positions.clear();
475+ self.failed_prefix_positions.clear();
476+ self.failed_reserved_word_prefix_positions.clear();
463477 self
464478 }
465479
@@ -1731,6 +1745,23 @@ impl<'a> Parser<'a> {
17311745 return prefix;
17321746 }
17331747
1748+ // Memoize parse_prefix failures to break 2^N speculation when both
1749+ // prefix arms fail at every level (e.g. `IF(current_time(...x`).
1750+ // The per-arm cache in `parse_prefix_inner` complements this for
1751+ // chains where the reserved arm fails but the unreserved fallback
1752+ // succeeds (e.g. `case-case-...c`).
1753+ let start_index = self.index;
1754+ if let Some(cached) = self.failed_prefix_positions.get(&start_index) {
1755+ return Err(cached.clone());
1756+ }
1757+ let result = self.parse_prefix_inner();
1758+ if let Err(ref e) = result {
1759+ self.failed_prefix_positions.insert(start_index, e.clone());
1760+ }
1761+ result
1762+ }
1763+
1764+ fn parse_prefix_inner(&mut self) -> Result<Expr, ParserError> {
17341765 // PostgreSQL allows any string literal to be preceded by a type name, indicating that the
17351766 // string literal represents a literal of that type. Some examples:
17361767 //
@@ -1826,7 +1857,21 @@ impl<'a> Parser<'a> {
18261857 {
18271858 return self.parse_expr_prefix_by_unreserved_word(&w, span);
18281859 }
1829- match self.try_parse(|parser| parser.parse_expr_prefix_by_reserved_word(&w, span)) {
1860+ // Memoize failed speculative reserved-word parses. When
1861+ // the reserved arm (CASE, CURRENT_TIME, etc.) does
1862+ // exponential work but the unreserved fallback ultimately
1863+ // succeeds, the overall `parse_prefix` returns `Ok` and the
1864+ // outer cache never fires. Chains like `case-case-...c`
1865+ // need this per-arm cache to break the doubling.
1866+ let try_parse_result = if let Some(cached) = self
1867+ .failed_reserved_word_prefix_positions
1868+ .get(&next_token_index)
1869+ {
1870+ Err(cached.clone())
1871+ } else {
1872+ self.try_parse(|parser| parser.parse_expr_prefix_by_reserved_word(&w, span))
1873+ };
1874+ match try_parse_result {
18301875 // This word indicated an expression prefix and parsing was successful
18311876 Ok(Some(expr)) => Ok(expr),
18321877
@@ -1843,6 +1888,8 @@ impl<'a> Parser<'a> {
18431888 if w.keyword == Keyword::NOT {
18441889 self.failed_unary_not_positions.insert(self.index);
18451890 }
1891+ self.failed_reserved_word_prefix_positions
1892+ .insert(next_token_index, e.clone());
18461893 if !self.dialect.is_reserved_for_identifier(w.keyword) {
18471894 if let Ok(Some(expr)) = self.maybe_parse(|parser| {
18481895 parser.parse_expr_prefix_by_unreserved_word(&w, span)
0 commit comments