Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2028,14 +2028,16 @@ impl<'a> Parser<'a> {
chain.push(AccessExpr::Dot(expr));
self.advance_token(); // The consumed placeholder
}
// Fallback to parsing an arbitrary expression, but restrict to expression
// types that are valid after the dot operator. This ensures that e.g.
// `T.interval` is parsed as a compound identifier, not as an interval
// expression.
// Parse a single field component, restricted to expression types valid
// after `.` (so e.g. `T.interval` is a compound identifier, not an
// interval expression). Using `parse_prefix` here rather than
// `parse_subexpr` avoids 2^N work on inputs like `IF a.b.c...x.#`:
// the outer loop already consumes successive `.field` segments, so a
// recursive `parse_subexpr` would re-walk the rest of the chain at
// every dot.
_ => {
let expr = self.maybe_parse(|parser| {
let expr = parser
.parse_subexpr(parser.dialect.prec_value(Precedence::Period))?;
let expr = parser.parse_prefix()?;
match &expr {
Expr::CompoundFieldAccess { .. }
| Expr::CompoundIdentifier(_)
Expand All @@ -2050,14 +2052,9 @@ impl<'a> Parser<'a> {
})?;

match expr {
// If we get back a compound field access or identifier,
// we flatten the nested expression.
// For example if the current root is `foo`
// and we get back a compound identifier expression `bar.baz`
// The full expression should be `foo.bar.baz` (i.e.
// a root with an access chain with 2 entries) and not
// `foo.(bar.baz)` (i.e. a root with an access chain with
// 1 entry`).
// `parse_prefix` does not itself follow compound chains, but a
// dialect override could still return a compound expression, so
// keep the flatten arms for safety.
Some(Expr::CompoundFieldAccess { root, access_chain }) => {
chain.push(AccessExpr::Dot(*root));
chain.extend(access_chain);
Expand Down
24 changes: 24 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9243,3 +9243,27 @@ fn parse_lock_table() {
}
}
}

/// `parse_compound_expr` used to do 2^N work on `IF a.b.c...x.#` because every
/// `.` re-entered `parse_subexpr` over the rest of the chain.
#[test]
fn parse_compound_chain_no_exponential_blowup() {
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

let chain: String = (0..30)
.map(|i| format!("a{i}"))
.collect::<Vec<_>>()
.join(".");
let sql = format!("IF {chain}.#");

let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let _ = sqlparser::parser::Parser::parse_sql(&PostgreSqlDialect {}, &sql);
let _ = tx.send(());
});

rx.recv_timeout(Duration::from_secs(5))
.expect("parser should reject this quickly, not loop exponentially");
}
Loading