diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8a9a67a74..6225855c1 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1963,7 +1963,8 @@ impl fmt::Display for Expr { | UnaryOperator::DoubleAt | UnaryOperator::QuestionDash | UnaryOperator::QuestionPipe - ) { + ) || matches!(expr.as_ref(), Expr::UnaryOp { .. }) + { write!(f, "{op} {expr}") } else { write!(f, "{op}{expr}") diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 0800bc41f..34b89c60a 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -19679,3 +19679,11 @@ fn parse_function_arg_call_chain_no_exponential_blowup() { rx.recv_timeout(Duration::from_secs(5)) .expect("parser should reject this quickly, not loop exponentially"); } + +#[test] +fn parse_nested_unary_ops() { + all_dialects().verified_stmt("SELECT - -1"); + all_dialects().verified_stmt("SELECT ~ ~1"); + all_dialects().verified_stmt("SELECT NOT NOT a"); + all_dialects().one_statement_parses_to("SELECT ~ ~ 1", "SELECT ~ ~1"); +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index a7128eafd..b053362e5 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -9663,3 +9663,29 @@ fn parse_right_deep_join_chain() { // NATURAL JOIN followed by a constrained join must stay left-associative. pg().verified_stmt("SELECT * FROM t0 NATURAL JOIN t1 INNER JOIN t2 ON true"); } + +#[test] +fn parse_nested_pg_unary_ops() { + let select = pg().verified_only_select("SELECT @ @1"); + assert_eq!( + SelectItem::UnnamedExpr(Expr::UnaryOp { + op: UnaryOperator::PGAbs, + expr: Box::new(Expr::UnaryOp { + op: UnaryOperator::PGAbs, + expr: Box::new(Expr::value(number("1"))), + }), + }), + select.projection[0] + ); + + let select = pg().verified_only_select("SELECT @@ 1"); + assert_eq!( + SelectItem::UnnamedExpr(Expr::UnaryOp { + op: UnaryOperator::DoubleAt, + expr: Box::new(Expr::value(number("1"))), + }), + select.projection[0] + ); + + pg().verified_stmt("SELECT |/ |/1"); +}