Skip to content

Commit 971cf99

Browse files
authored
fix ^ evaluates as bitwise XOR instead of exponentiation (#22314)
## Which issue does this PR close? - Closes #22252. ## Rationale for this change In PostgreSQL, the `^` operator represents exponentiation, but DataFusion was interpreting it as bitwise XOR. This caused PostgreSQL-dialect queries such as `SELECT 2 ^ 3;` to return `1` instead of `8`. This change aligns DataFusion's PostgreSQL SQL semantics with PostgreSQL for this operator while preserving existing non-PostgreSQL behavior. ## What changes are included in this PR? - Added PostgreSQL-specific handling for `BinaryOperator::PGExp` during SQL expression lowering. - Lowered PostgreSQL `^` expressions to the built-in `power(left, right)` scalar function instead of treating them as bitwise XOR. - Kept existing generic-dialect `^` behavior unchanged. - Kept PostgreSQL bitwise XOR behavior unchanged via `#`. - Added a sqllogictest regression covering `SELECT 2 ^ 3;` under the PostgreSQL parser dialect. ## Are these changes tested? Yes. Added a regression test in scalar.slt to verify that PostgreSQL-dialect `^` is evaluated as exponentiation. Validated with: ```bash cargo test -p datafusion-sqllogictest --test sqllogictests scalar ``` ## Are there any user-facing changes? Yes. For the PostgreSQL SQL parser dialect, `^` now behaves as exponentiation instead of bitwise XOR, matching PostgreSQL semantics. Generic-dialect behavior is unchanged, and PostgreSQL bitwise XOR remains available through `#`.
1 parent 097efae commit 971cf99

3 files changed

Lines changed: 40 additions & 6 deletions

File tree

datafusion/sql/src/expr/binary_op.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
// under the License.
1717

1818
use crate::planner::{ContextProvider, SqlToRel};
19-
use datafusion_common::{Result, not_impl_err};
19+
use datafusion_common::{Result, internal_datafusion_err, not_impl_err};
2020
use datafusion_expr::Operator;
21+
use datafusion_expr::expr::ScalarFunction;
22+
use datafusion_expr::{BinaryExpr, Expr};
2123
use sqlparser::ast::BinaryOperator;
2224

2325
impl<S: ContextProvider> SqlToRel<'_, S> {
@@ -72,4 +74,34 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
7274
_ => not_impl_err!("Unsupported binary operator: {:?}", op),
7375
}
7476
}
77+
78+
pub(crate) fn build_binary_expr(
79+
&self,
80+
op: &BinaryOperator,
81+
left: Expr,
82+
right: Expr,
83+
) -> Result<Expr> {
84+
if matches!(op, BinaryOperator::PGExp) {
85+
let fun_name = "power";
86+
let fun = self
87+
.context_provider
88+
.get_function_meta(fun_name)
89+
.ok_or_else(|| {
90+
internal_datafusion_err!(
91+
"Unable to find expected '{fun_name}' function"
92+
)
93+
})?;
94+
95+
return Ok(Expr::ScalarFunction(ScalarFunction::new_udf(
96+
fun,
97+
vec![left, right],
98+
)));
99+
}
100+
101+
Ok(Expr::BinaryExpr(BinaryExpr::new(
102+
Box::new(left),
103+
self.parse_sql_binary_op(op)?,
104+
Box::new(right),
105+
)))
106+
}
75107
}

datafusion/sql/src/expr/mod.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
142142
}
143143

144144
let RawBinaryExpr { op, left, right } = binary_expr;
145-
Ok(Expr::BinaryExpr(BinaryExpr::new(
146-
Box::new(left),
147-
self.parse_sql_binary_op(&op)?,
148-
Box::new(right),
149-
)))
145+
self.build_binary_expr(&op, left, right)
150146
}
151147

152148
pub fn sql_to_expr_with_alias(

datafusion/sqllogictest/test_files/scalar.slt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,12 @@ NULL -32
13001300
statement ok
13011301
set datafusion.sql_parser.dialect = postgresql;
13021302

1303+
# postgresql exponentiation uses caret
1304+
query R
1305+
select 2 ^ 3;
1306+
----
1307+
8
1308+
13031309
# postgresql bitwise xor with column and scalar
13041310
query I rowsort
13051311
select c # 856 from signed_integers;

0 commit comments

Comments
 (0)