datafusion_physical_expr/expressions/
not.rs1use std::fmt;
21use std::hash::Hash;
22use std::sync::Arc;
23
24use crate::PhysicalExpr;
25
26use arrow::datatypes::{DataType, FieldRef, Schema};
27use arrow::record_batch::RecordBatch;
28use datafusion_common::{Result, ScalarValue, cast::as_boolean_array, internal_err};
29use datafusion_expr::ColumnarValue;
30use datafusion_expr::interval_arithmetic::Interval;
31#[expect(deprecated)]
32use datafusion_expr::statistics::Distribution::{self, Bernoulli};
33
34#[derive(Debug, Eq)]
36pub struct NotExpr {
37 arg: Arc<dyn PhysicalExpr>,
39}
40
41impl PartialEq for NotExpr {
43 fn eq(&self, other: &Self) -> bool {
44 self.arg.eq(&other.arg)
45 }
46}
47
48impl Hash for NotExpr {
49 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
50 self.arg.hash(state);
51 }
52}
53
54impl NotExpr {
55 pub fn new(arg: Arc<dyn PhysicalExpr>) -> Self {
57 Self { arg }
58 }
59
60 pub fn arg(&self) -> &Arc<dyn PhysicalExpr> {
62 &self.arg
63 }
64}
65
66impl fmt::Display for NotExpr {
67 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68 write!(f, "NOT {}", self.arg)
69 }
70}
71
72impl PhysicalExpr for NotExpr {
73 fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
74 Ok(DataType::Boolean)
75 }
76
77 fn nullable(&self, input_schema: &Schema) -> Result<bool> {
78 self.arg.nullable(input_schema)
79 }
80
81 fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
82 match self.arg.evaluate(batch)? {
83 ColumnarValue::Array(array) => {
84 let array = as_boolean_array(&array)?;
85 Ok(ColumnarValue::Array(Arc::new(
86 arrow::compute::kernels::boolean::not(array)?,
87 )))
88 }
89 ColumnarValue::Scalar(scalar) => {
90 if scalar.is_null() {
91 return Ok(ColumnarValue::Scalar(ScalarValue::Boolean(None)));
92 }
93 let bool_value: bool = scalar.try_into()?;
94 Ok(ColumnarValue::Scalar(ScalarValue::from(!bool_value)))
95 }
96 }
97 }
98
99 fn return_field(&self, input_schema: &Schema) -> Result<FieldRef> {
100 self.arg.return_field(input_schema)
101 }
102
103 fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
104 vec![&self.arg]
105 }
106
107 fn with_new_children(
108 self: Arc<Self>,
109 children: Vec<Arc<dyn PhysicalExpr>>,
110 ) -> Result<Arc<dyn PhysicalExpr>> {
111 Ok(Arc::new(NotExpr::new(Arc::clone(&children[0]))))
112 }
113
114 fn evaluate_bounds(&self, children: &[&Interval]) -> Result<Interval> {
115 children[0].not()
116 }
117
118 fn propagate_constraints(
119 &self,
120 interval: &Interval,
121 children: &[&Interval],
122 ) -> Result<Option<Vec<Interval>>> {
123 let complemented_interval = interval.not()?;
124
125 Ok(children[0]
126 .intersect(complemented_interval)?
127 .map(|result| vec![result]))
128 }
129
130 #[expect(deprecated)]
131 fn evaluate_statistics(&self, children: &[&Distribution]) -> Result<Distribution> {
132 match children[0] {
133 Bernoulli(b) => {
134 let p_value = b.p_value();
135 if p_value.is_null() {
136 Ok(children[0].clone())
137 } else {
138 let one = ScalarValue::new_one(&p_value.data_type())?;
139 Distribution::new_bernoulli(one.sub_checked(p_value)?)
140 }
141 }
142 _ => internal_err!("NotExpr can only operate on Boolean datatypes"),
143 }
144 }
145
146 #[expect(deprecated)]
147 fn propagate_statistics(
148 &self,
149 parent: &Distribution,
150 children: &[&Distribution],
151 ) -> Result<Option<Vec<Distribution>>> {
152 match (parent, children[0]) {
153 (Bernoulli(parent), Bernoulli(child)) => {
154 let parent_range = parent.range();
155 let result = if parent_range == Interval::TRUE {
156 if child.range() == Interval::TRUE {
157 None
158 } else {
159 Some(vec![Distribution::new_bernoulli(ScalarValue::new_zero(
160 &child.data_type(),
161 )?)?])
162 }
163 } else if parent_range == Interval::FALSE {
164 if child.range() == Interval::FALSE {
165 None
166 } else {
167 Some(vec![Distribution::new_bernoulli(ScalarValue::new_one(
168 &child.data_type(),
169 )?)?])
170 }
171 } else {
172 Some(vec![])
173 };
174 Ok(result)
175 }
176 _ => internal_err!("NotExpr can only operate on Boolean datatypes"),
177 }
178 }
179
180 fn fmt_sql(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 write!(f, "NOT ")?;
182 self.arg.fmt_sql(f)
183 }
184
185 #[cfg(feature = "proto")]
186 fn try_to_proto(
187 &self,
188 ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
189 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalExprNode>> {
190 use datafusion_proto_models::protobuf;
191
192 Ok(Some(protobuf::PhysicalExprNode {
193 expr_id: None,
194 expr_type: Some(protobuf::physical_expr_node::ExprType::NotExpr(Box::new(
195 protobuf::PhysicalNot {
196 expr: Some(Box::new(ctx.encode_child(&self.arg)?)),
197 },
198 ))),
199 }))
200 }
201}
202
203#[cfg(feature = "proto")]
204impl NotExpr {
205 pub fn try_from_proto(
207 node: &datafusion_proto_models::protobuf::PhysicalExprNode,
208 ctx: &datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx<'_>,
209 ) -> Result<Arc<dyn PhysicalExpr>> {
210 use datafusion_physical_expr_common::expect_expr_variant;
211 use datafusion_proto_models::protobuf;
212
213 let not_expr = expect_expr_variant!(
214 node,
215 protobuf::physical_expr_node::ExprType::NotExpr,
216 "NotExpr",
217 );
218 let expr =
219 ctx.decode_required_expression(not_expr.expr.as_deref(), "NotExpr", "expr")?;
220
221 Ok(Arc::new(NotExpr::new(expr)))
222 }
223}
224
225pub fn not(arg: Arc<dyn PhysicalExpr>) -> Result<Arc<dyn PhysicalExpr>> {
227 Ok(Arc::new(NotExpr::new(arg)))
228}
229
230#[cfg(test)]
231mod tests {
232 use std::sync::LazyLock;
233
234 use super::*;
235 use crate::expressions::{Column, col};
236
237 use arrow::{array::BooleanArray, datatypes::*};
238 use datafusion_physical_expr_common::physical_expr::fmt_sql;
239
240 #[test]
241 fn neg_op() -> Result<()> {
242 let schema = schema();
243
244 let expr = not(col("a", &schema)?)?;
245 assert_eq!(expr.data_type(&schema)?, DataType::Boolean);
246 assert!(expr.nullable(&schema)?);
247
248 let input = BooleanArray::from(vec![Some(true), None, Some(false)]);
249 let expected = &BooleanArray::from(vec![Some(false), None, Some(true)]);
250
251 let batch = RecordBatch::try_new(schema, vec![Arc::new(input)])?;
252
253 let result = expr
254 .evaluate(&batch)?
255 .into_array(batch.num_rows())
256 .expect("Failed to convert to array");
257 let result =
258 as_boolean_array(&result).expect("failed to downcast to BooleanArray");
259 assert_eq!(result, expected);
260
261 Ok(())
262 }
263
264 #[test]
265 fn test_evaluate_bounds() -> Result<()> {
266 assert_evaluate_bounds(
272 Interval::make(Some(false), Some(true))?,
273 Interval::make(Some(false), Some(true))?,
274 )?;
275 assert_evaluate_bounds(
278 Interval::make(Some(true), Some(true))?,
279 Interval::make(Some(false), Some(false))?,
280 )?;
281 assert_evaluate_bounds(
282 Interval::make(Some(false), Some(false))?,
283 Interval::make(Some(true), Some(true))?,
284 )?;
285 Ok(())
286 }
287
288 fn assert_evaluate_bounds(
289 interval: Interval,
290 expected_interval: Interval,
291 ) -> Result<()> {
292 let not_expr = not(col("a", &schema())?)?;
293 assert_eq!(not_expr.evaluate_bounds(&[&interval])?, expected_interval);
294 Ok(())
295 }
296
297 #[test]
298 #[expect(deprecated)]
299 fn test_evaluate_statistics() -> Result<()> {
300 let _schema = &Schema::new(vec![Field::new("a", DataType::Boolean, false)]);
301 let a = Arc::new(Column::new("a", 0)) as _;
302 let expr = not(a)?;
303
304 assert!(
306 expr.evaluate_statistics(&[&Distribution::new_uniform(
307 Interval::make_unbounded(&DataType::Float64)?
308 )?])
309 .is_err()
310 );
311
312 assert!(
314 expr.evaluate_statistics(&[&Distribution::new_exponential(
315 ScalarValue::from(1.0),
316 ScalarValue::from(1.0),
317 true
318 )?])
319 .is_err()
320 );
321
322 assert!(
324 expr.evaluate_statistics(&[&Distribution::new_gaussian(
325 ScalarValue::from(1.0),
326 ScalarValue::from(1.0),
327 )?])
328 .is_err()
329 );
330
331 assert_eq!(
333 expr.evaluate_statistics(&[&Distribution::new_bernoulli(
334 ScalarValue::from(0.0),
335 )?])?,
336 Distribution::new_bernoulli(ScalarValue::from(1.))?
337 );
338
339 assert_eq!(
340 expr.evaluate_statistics(&[&Distribution::new_bernoulli(
341 ScalarValue::from(1.0),
342 )?])?,
343 Distribution::new_bernoulli(ScalarValue::from(0.))?
344 );
345
346 assert_eq!(
347 expr.evaluate_statistics(&[&Distribution::new_bernoulli(
348 ScalarValue::from(0.25),
349 )?])?,
350 Distribution::new_bernoulli(ScalarValue::from(0.75))?
351 );
352
353 assert!(
354 expr.evaluate_statistics(&[&Distribution::new_generic(
355 ScalarValue::Null,
356 ScalarValue::Null,
357 ScalarValue::Null,
358 Interval::make_unbounded(&DataType::UInt8)?
359 )?])
360 .is_err()
361 );
362
363 assert!(
365 expr.evaluate_statistics(&[&Distribution::new_generic(
366 ScalarValue::Null,
367 ScalarValue::Null,
368 ScalarValue::Null,
369 Interval::make_unbounded(&DataType::Float64)?
370 )?])
371 .is_err()
372 );
373
374 Ok(())
375 }
376
377 #[test]
378 fn test_fmt_sql() -> Result<()> {
379 let schema = schema();
380
381 let expr = not(col("a", &schema)?)?;
382
383 let display_string = expr.to_string();
384 assert_eq!(display_string, "NOT a@0");
385
386 let sql_string = fmt_sql(expr.as_ref()).to_string();
387 assert_eq!(sql_string, "NOT a");
388
389 Ok(())
390 }
391
392 fn schema() -> SchemaRef {
393 static SCHEMA: LazyLock<SchemaRef> = LazyLock::new(|| {
394 Arc::new(Schema::new(vec![Field::new("a", DataType::Boolean, true)]))
395 });
396 Arc::clone(&SCHEMA)
397 }
398}
399
400#[cfg(all(test, feature = "proto"))]
402mod proto_tests {
403 use super::*;
404 use crate::expressions::{Column, col};
405 use crate::proto_test_util::{
406 StubDecoder, StubEncoder, UnreachableDecoder, column_node,
407 };
408 use arrow::datatypes::Field;
409 use datafusion_common::DataFusionError;
410 use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
411 use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
412 use datafusion_proto_models::protobuf::{
413 PhysicalExprNode, PhysicalNot, physical_expr_node,
414 };
415
416 fn not_node(expr: Option<Box<PhysicalExprNode>>) -> PhysicalExprNode {
418 PhysicalExprNode {
419 expr_id: None,
420 expr_type: Some(physical_expr_node::ExprType::NotExpr(Box::new(
421 PhysicalNot { expr },
422 ))),
423 }
424 }
425
426 fn not_fixture() -> NotExpr {
428 let schema = Schema::new(vec![Field::new("a", DataType::Boolean, true)]);
429 NotExpr::new(col("a", &schema).unwrap())
430 }
431
432 #[test]
433 fn try_to_proto_encodes_not_expr() {
434 let not = not_fixture();
435 let encoder = StubEncoder::ok();
436 let ctx = PhysicalExprEncodeCtx::new(&encoder);
437
438 let node = not
439 .try_to_proto(&ctx)
440 .unwrap()
441 .expect("NotExpr should encode to Some(node)");
442
443 assert!(node.expr_id.is_none());
444 let not_node = match node.expr_type {
445 Some(physical_expr_node::ExprType::NotExpr(boxed)) => *boxed,
446 other => panic!("expected a NotExpr node, got {other:?}"),
447 };
448 assert!(not_node.expr.is_some());
449 }
450
451 #[test]
452 fn try_to_proto_propagates_expr_encode_error() {
453 let not = not_fixture();
454 let encoder = StubEncoder::failing_on(1);
455 let ctx = PhysicalExprEncodeCtx::new(&encoder);
456 let err = not.try_to_proto(&ctx).unwrap_err();
457 assert!(matches!(err, DataFusionError::Internal(msg) if msg.contains("call 1")));
458 }
459
460 #[test]
461 fn try_from_proto_decodes_not_expr() {
462 let node = not_node(Some(Box::new(column_node("a"))));
463 let schema = Schema::empty();
464 let decoder = StubDecoder::ok();
465 let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
466
467 let decoded = NotExpr::try_from_proto(&node, &ctx).unwrap();
468 let not = decoded
469 .downcast_ref::<NotExpr>()
470 .expect("decoded expr should be a NotExpr");
471 assert!(not.arg().downcast_ref::<Column>().is_some());
472 }
473
474 #[test]
475 fn try_from_proto_rejects_non_not_node() {
476 let node = column_node("a");
477 let schema = Schema::empty();
478 let decoder = UnreachableDecoder;
479 let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
480 let err = NotExpr::try_from_proto(&node, &ctx).unwrap_err();
481 assert!(
482 matches!(err, DataFusionError::Internal(msg) if msg.contains("PhysicalExprNode is not a NotExpr"))
483 );
484 }
485
486 #[test]
487 fn try_from_proto_rejects_missing_expr() {
488 let node = not_node(None);
489 let schema = Schema::empty();
490 let decoder = UnreachableDecoder;
491 let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
492 let err = NotExpr::try_from_proto(&node, &ctx).unwrap_err();
493 assert!(
494 matches!(err, DataFusionError::Internal(msg) if msg.contains("NotExpr is missing required field 'expr'"))
495 );
496 }
497
498 #[test]
499 fn try_from_proto_propagates_expr_decode_error() {
500 let node = not_node(Some(Box::new(column_node("a"))));
501 let schema = Schema::empty();
502 let decoder = StubDecoder::failing_on(1);
503 let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
504 let err = NotExpr::try_from_proto(&node, &ctx).unwrap_err();
505 assert!(matches!(err, DataFusionError::Internal(msg) if msg.contains("call 1")));
506 }
507}