Skip to content
Closed
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
8 changes: 8 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,14 @@ pub trait Dialect: Debug + Any {
self.prec_value(Precedence::Between)
}

/// Precedence used to stop parsing the operand of the unary `+` and `-` operators.
///
/// Defaults to `MulDivModOp`, which is right for dialects where no binary operator binds
/// tighter than `*`, `/` and `%`.
fn unary_operand_precedence(&self) -> u8 {
self.prec_value(Precedence::MulDivModOp)
}

/// Returns true if the low bound of a `BETWEEN` predicate may end with one comparison,
/// as in `a BETWEEN b > 1 AND c`, while `IS`, `LIKE` and `BETWEEN` still end the bound.
///
Expand Down
8 changes: 8 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ const PERIOD_PREC: u8 = 200;
const DOUBLE_COLON_PREC: u8 = 140;
const BRACKET_PREC: u8 = 130;
const COLLATE_PREC: u8 = 120;
// `UMINUS` sits between `COLLATE`/`AT` and `'[' ']'` in gram.y, so a unary `+`/`-` operand may
// still take `.`, `::` and `[]`, but no other operator:
// <https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-PRECEDENCE>
const UNARY_OPERAND_PREC: u8 = COLLATE_PREC;
const AT_TZ_PREC: u8 = 110;
const CARET_PREC: u8 = 100;
const MUL_DIV_MOD_OP_PREC: u8 = 90;
Expand Down Expand Up @@ -390,6 +394,10 @@ impl Dialect for PostgreSqlDialect {
}
}

fn unary_operand_precedence(&self) -> u8 {
UNARY_OPERAND_PREC
}

fn allow_extract_custom(&self) -> bool {
true
}
Expand Down
4 changes: 1 addition & 3 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2017,9 +2017,7 @@ impl<'a> Parser<'a> {
};
Ok(Expr::UnaryOp {
op,
expr: Box::new(
self.parse_subexpr(self.dialect.prec_value(Precedence::MulDivModOp))?,
),
expr: Box::new(self.parse_subexpr(self.dialect.unary_operand_precedence())?),
})
}
Token::ExclamationMark if dialect.supports_bang_not_operator() => Ok(Expr::UnaryOp {
Expand Down
44 changes: 44 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13430,3 +13430,47 @@ fn display_maintain_privilege_propagates_formatter_errors() {
Err(fmt::Error)
);
}

#[test]
fn parse_unary_operand_binds_tighter_than_exponentiation() {
// `UMINUS` is declared above `^` in gram.y, so `-1 ^ 0` means `(-1) ^ 0`.
let minus_one = Expr::UnaryOp {
op: UnaryOperator::Minus,
expr: Box::new(Expr::value(number("1"))),
};
let plus_two = Expr::UnaryOp {
op: UnaryOperator::Plus,
expr: Box::new(Expr::value(number("2"))),
};

assert_eq!(
pg().verified_expr("-1 ^ 0"),
Expr::BinaryOp {
left: Box::new(minus_one),
op: BinaryOperator::PGExp,
right: Box::new(Expr::value(number("0"))),
}
);
assert_eq!(
pg().verified_expr("+2 ^ 2"),
Expr::BinaryOp {
left: Box::new(plus_two),
op: BinaryOperator::PGExp,
right: Box::new(Expr::value(number("2"))),
}
);

// Operators that bind looser than `^` keep the unary operator on the left-hand side.
let minus_a = Expr::UnaryOp {
op: UnaryOperator::Minus,
expr: Box::new(Expr::Identifier(Ident::new("a"))),
};
assert_eq!(
pg().verified_expr("-a * b"),
Expr::BinaryOp {
left: Box::new(minus_a),
op: BinaryOperator::Multiply,
right: Box::new(Expr::Identifier(Ident::new("b"))),
}
);
}
Loading