diff --git a/datafusion/common/src/scalar.rs b/datafusion/common/src/scalar.rs index dff97d2f981f1..c236623abfe9e 100644 --- a/datafusion/common/src/scalar.rs +++ b/datafusion/common/src/scalar.rs @@ -762,6 +762,24 @@ impl ScalarValue { } } + /// whether this value is true or not. + pub fn is_true(&self) -> Result { + match self { + ScalarValue::Boolean(Some(v)) => Ok(*v), + ScalarValue::Null => Ok(false), + e => Err(DataFusionError::Execution(format!("Cannot apply 'IS TRUE' to arguments of type '<{}> IS TRUE'. Supported form(s): ' IS TRUE'", e.get_datatype()))) + } + } + + /// whether this value is false or not. + pub fn is_false(&self) -> Result { + match self { + ScalarValue::Boolean(Some(v)) => Ok(!v), + ScalarValue::Null => Ok(false), + e => Err(DataFusionError::Execution(format!("Cannot apply 'IS FALSE' to arguments of type '<{}> IS FALSE'. Supported form(s): ' IS FALSE'", e.get_datatype()))) + } + } + /// Converts a scalar value into an 1-row array. pub fn to_array(&self) -> ArrayRef { self.to_array_of_size(1) diff --git a/datafusion/core/src/datasource/listing/helpers.rs b/datafusion/core/src/datasource/listing/helpers.rs index 6c018eda3e76b..7f36e2a5dd844 100644 --- a/datafusion/core/src/datasource/listing/helpers.rs +++ b/datafusion/core/src/datasource/listing/helpers.rs @@ -82,6 +82,8 @@ impl ExpressionVisitor for ApplicabilityVisitor<'_> { | Expr::Alias(_, _) | Expr::ScalarVariable(_, _) | Expr::Not(_) + | Expr::IsTrue(_) + | Expr::IsFalse(_) | Expr::IsNotNull(_) | Expr::IsNull(_) | Expr::Negative(_) diff --git a/datafusion/core/src/physical_plan/planner.rs b/datafusion/core/src/physical_plan/planner.rs index 46e09ff27445e..9431094ad092d 100644 --- a/datafusion/core/src/physical_plan/planner.rs +++ b/datafusion/core/src/physical_plan/planner.rs @@ -148,6 +148,14 @@ fn create_physical_name(e: &Expr, is_first_expr: bool) -> Result { let expr = create_physical_name(expr, false)?; Ok(format!("{} IS NULL", expr)) } + Expr::IsTrue(expr) => { + let expr = create_physical_name(expr, false)?; + Ok(format!("{} IS TRUE", expr)) + } + Expr::IsFalse(expr) => { + let expr = create_physical_name(expr, false)?; + Ok(format!("{} IS FALSE", expr)) + } Expr::IsNotNull(expr) => { let expr = create_physical_name(expr, false)?; Ok(format!("{} IS NOT NULL", expr)) diff --git a/datafusion/expr/src/expr.rs b/datafusion/expr/src/expr.rs index ba6f7a96c29d3..48990b2c2c3eb 100644 --- a/datafusion/expr/src/expr.rs +++ b/datafusion/expr/src/expr.rs @@ -102,6 +102,10 @@ pub enum Expr { }, /// Negation of an expression. The expression's type must be a boolean to make sense. Not(Box), + /// Whether an expression is true. This expression is never null. + IsTrue(Box), + /// Whether an expression is false. This expression is never null. + IsFalse(Box), /// Whether an expression is not Null. This expression is never null. IsNotNull(Box), /// Whether an expression is Null. This expression is never null. @@ -333,6 +337,8 @@ impl Expr { Expr::GroupingSet(..) => "GroupingSet", Expr::InList { .. } => "InList", Expr::InSubquery { .. } => "InSubquery", + Expr::IsTrue(..) => "IsTrue", + Expr::IsFalse(..) => "IsFalse", Expr::IsNotNull(..) => "IsNotNull", Expr::IsNull(..) => "IsNull", Expr::Literal(..) => "Literal", @@ -433,6 +439,18 @@ impl Expr { Expr::IsNull(Box::new(self)) } + /// Return `IsTrue(Box(self)) + #[allow(clippy::wrong_self_convention)] + pub fn is_true(self) -> Expr { + Expr::IsTrue(Box::new(self)) + } + + /// Return `IsFalse(Box(self)) + #[allow(clippy::wrong_self_convention)] + pub fn is_false(self) -> Expr { + Expr::IsFalse(Box::new(self)) + } + /// Return `IsNotNull(Box(self)) #[allow(clippy::wrong_self_convention)] pub fn is_not_null(self) -> Expr { @@ -547,6 +565,8 @@ impl fmt::Debug for Expr { Expr::Not(expr) => write!(f, "NOT {:?}", expr), Expr::Negative(expr) => write!(f, "(- {:?})", expr), Expr::IsNull(expr) => write!(f, "{:?} IS NULL", expr), + Expr::IsTrue(expr) => write!(f, "{:?} IS TRUE", expr), + Expr::IsFalse(expr) => write!(f, "{:?} IS FALSE", expr), Expr::IsNotNull(expr) => write!(f, "{:?} IS NOT NULL", expr), Expr::Exists { subquery, @@ -795,6 +815,14 @@ fn create_name(e: &Expr, input_schema: &DFSchema) -> Result { let expr = create_name(expr, input_schema)?; Ok(format!("{} IS NULL", expr)) } + Expr::IsTrue(expr) => { + let expr = create_name(expr, input_schema)?; + Ok(format!("{} IS TRUE", expr)) + } + Expr::IsFalse(expr) => { + let expr = create_name(expr, input_schema)?; + Ok(format!("{} IS FALSE", expr)) + } Expr::IsNotNull(expr) => { let expr = create_name(expr, input_schema)?; Ok(format!("{} IS NOT NULL", expr)) diff --git a/datafusion/expr/src/expr_rewriter.rs b/datafusion/expr/src/expr_rewriter.rs index e8cf049dde6f7..73a450d748012 100644 --- a/datafusion/expr/src/expr_rewriter.rs +++ b/datafusion/expr/src/expr_rewriter.rs @@ -128,6 +128,8 @@ impl ExprRewritable for Expr { right: rewrite_boxed(right, rewriter)?, }, Expr::Not(expr) => Expr::Not(rewrite_boxed(expr, rewriter)?), + Expr::IsTrue(expr) => Expr::IsTrue(rewrite_boxed(expr, rewriter)?), + Expr::IsFalse(expr) => Expr::IsFalse(rewrite_boxed(expr, rewriter)?), Expr::IsNotNull(expr) => Expr::IsNotNull(rewrite_boxed(expr, rewriter)?), Expr::IsNull(expr) => Expr::IsNull(rewrite_boxed(expr, rewriter)?), Expr::Negative(expr) => Expr::Negative(rewrite_boxed(expr, rewriter)?), diff --git a/datafusion/expr/src/expr_schema.rs b/datafusion/expr/src/expr_schema.rs index bbb414655c266..7306b0b2b1f94 100644 --- a/datafusion/expr/src/expr_schema.rs +++ b/datafusion/expr/src/expr_schema.rs @@ -104,6 +104,8 @@ impl ExprSchemable for Expr { | Expr::InSubquery { .. } | Expr::Between { .. } | Expr::InList { .. } + | Expr::IsTrue(_) + | Expr::IsFalse(_) | Expr::IsNotNull(_) => Ok(DataType::Boolean), Expr::ScalarSubquery(subquery) => { Ok(subquery.subquery.schema().field(0).data_type().clone()) @@ -183,7 +185,11 @@ impl ExprSchemable for Expr { | Expr::WindowFunction { .. } | Expr::AggregateFunction { .. } | Expr::AggregateUDF { .. } => Ok(true), - Expr::IsNull(_) | Expr::IsNotNull(_) | Expr::Exists { .. } => Ok(false), + Expr::IsNull(_) + | Expr::IsNotNull(_) + | Expr::IsTrue(_) + | Expr::IsFalse(_) + | Expr::Exists { .. } => Ok(false), Expr::InSubquery { expr, .. } => expr.nullable(input_schema), Expr::ScalarSubquery(subquery) => { Ok(subquery.subquery.schema().field(0).is_nullable()) diff --git a/datafusion/expr/src/expr_visitor.rs b/datafusion/expr/src/expr_visitor.rs index 162db60a03c9f..febdc81fe02dd 100644 --- a/datafusion/expr/src/expr_visitor.rs +++ b/datafusion/expr/src/expr_visitor.rs @@ -96,6 +96,8 @@ impl ExprVisitable for Expr { let visitor = match self { Expr::Alias(expr, _) | Expr::Not(expr) + | Expr::IsTrue(expr) + | Expr::IsFalse(expr) | Expr::IsNotNull(expr) | Expr::IsNull(expr) | Expr::Negative(expr) diff --git a/datafusion/expr/src/utils.rs b/datafusion/expr/src/utils.rs index 25ca8d6e36d62..c2ba4cd648b36 100644 --- a/datafusion/expr/src/utils.rs +++ b/datafusion/expr/src/utils.rs @@ -80,6 +80,8 @@ impl ExpressionVisitor for ColumnNameVisitor<'_> { | Expr::Literal(_) | Expr::BinaryExpr { .. } | Expr::Not(_) + | Expr::IsTrue(_) + | Expr::IsFalse(_) | Expr::IsNotNull(_) | Expr::IsNull(_) | Expr::Negative(_) diff --git a/datafusion/optimizer/src/common_subexpr_eliminate.rs b/datafusion/optimizer/src/common_subexpr_eliminate.rs index 8627b404dce85..a8a2702ca4ab1 100644 --- a/datafusion/optimizer/src/common_subexpr_eliminate.rs +++ b/datafusion/optimizer/src/common_subexpr_eliminate.rs @@ -408,6 +408,12 @@ impl ExprIdentifierVisitor<'_> { Expr::Not(_) => { desc.push_str("Not-"); } + Expr::IsTrue(_) => { + desc.push_str("IsTrue-"); + } + Expr::IsFalse(_) => { + desc.push_str("IsFalse-"); + } Expr::IsNotNull(_) => { desc.push_str("IsNotNull-"); } diff --git a/datafusion/optimizer/src/simplify_expressions.rs b/datafusion/optimizer/src/simplify_expressions.rs index 8bb829024f39a..25e77994e4c43 100644 --- a/datafusion/optimizer/src/simplify_expressions.rs +++ b/datafusion/optimizer/src/simplify_expressions.rs @@ -515,6 +515,8 @@ impl<'a> ConstEvaluator<'a> { Expr::Literal(_) | Expr::BinaryExpr { .. } | Expr::Not(_) + | Expr::IsTrue(_) + | Expr::IsFalse(_) | Expr::IsNotNull(_) | Expr::IsNull(_) | Expr::Negative(_) diff --git a/datafusion/physical-expr/src/expressions/is_false.rs b/datafusion/physical-expr/src/expressions/is_false.rs new file mode 100644 index 0000000000000..7401277673567 --- /dev/null +++ b/datafusion/physical-expr/src/expressions/is_false.rs @@ -0,0 +1,127 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! IS FALSE expression + +use std::{any::Any, sync::Arc}; + +use crate::PhysicalExpr; +use arrow::{ + array::{Array, BooleanArray, BooleanBuilder}, + datatypes::{DataType, Schema}, + record_batch::RecordBatch, +}; +use datafusion_common::DataFusionError; +use datafusion_common::Result; +use datafusion_common::ScalarValue; +use datafusion_expr::ColumnarValue; + +/// IS FALSE expression +#[derive(Debug)] +pub struct IsFalseExpr { + /// The input expression + arg: Arc, +} + +impl IsFalseExpr { + /// Create new not expression + pub fn new(arg: Arc) -> Self { + Self { arg } + } + + /// Get the input expression + pub fn arg(&self) -> &Arc { + &self.arg + } +} + +impl std::fmt::Display for IsFalseExpr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} IS FALSE", self.arg) + } +} + +impl PhysicalExpr for IsFalseExpr { + /// Return a reference to Any that can be used for downcasting + fn as_any(&self) -> &dyn Any { + self + } + + fn data_type(&self, _input_schema: &Schema) -> Result { + Ok(DataType::Boolean) + } + + fn nullable(&self, _input_schema: &Schema) -> Result { + Ok(false) + } + + fn evaluate(&self, batch: &RecordBatch) -> Result { + let arg = self.arg.evaluate(batch)?; + match arg { + ColumnarValue::Array(array) => { + match array.as_any().downcast_ref::() { + Some(bool_array) => { + let array_len = array.len(); + let mut result_builder = BooleanBuilder::new(array_len); + for i in 0..array_len { + result_builder.append_value(!bool_array.is_null(i) && !bool_array.value(i)); + } + Ok(ColumnarValue::Array(Arc::new(result_builder.finish()))) + } + _ => Err(DataFusionError::Execution(format!("Cannot apply 'IS FALSE' to arguments of type '<{:?}> IS FALSE'. Supported form(s): ' IS FALSE'", array.data_type()))) + } + } + ColumnarValue::Scalar(scalar) => Ok(ColumnarValue::Scalar( + ScalarValue::Boolean(Some(scalar.is_false()?)), + )), + } + } +} + +/// Create an IS FALSE expression +pub fn is_false(arg: Arc) -> Result> { + Ok(Arc::new(IsFalseExpr::new(arg))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::expressions::col; + use arrow::{array::BooleanArray, datatypes::*, record_batch::RecordBatch}; + use std::sync::Arc; + + #[test] + fn is_false_op() -> Result<()> { + let schema = Schema::new(vec![Field::new("a", DataType::Boolean, true)]); + let a = BooleanArray::from(vec![Some(true), Some(false), None]); + let expr = is_false(col("a", &schema)?).unwrap(); + let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)])?; + + // expression: "a is false" + let result = expr.evaluate(&batch)?.into_array(batch.num_rows()); + let result = result + .as_any() + .downcast_ref::() + .expect("failed to downcast to BooleanArray"); + + let expected = &BooleanArray::from(vec![false, true, false]); + + assert_eq!(expected, result); + + Ok(()) + } +} diff --git a/datafusion/physical-expr/src/expressions/is_true.rs b/datafusion/physical-expr/src/expressions/is_true.rs new file mode 100644 index 0000000000000..a5d08a0202dbb --- /dev/null +++ b/datafusion/physical-expr/src/expressions/is_true.rs @@ -0,0 +1,127 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! IS TRUE expression + +use std::{any::Any, sync::Arc}; + +use crate::PhysicalExpr; +use arrow::{ + array::{Array, BooleanArray, BooleanBuilder}, + datatypes::{DataType, Schema}, + record_batch::RecordBatch, +}; +use datafusion_common::DataFusionError; +use datafusion_common::Result; +use datafusion_common::ScalarValue; +use datafusion_expr::ColumnarValue; + +/// IS TRUE expression +#[derive(Debug)] +pub struct IsTrueExpr { + /// The input expression + arg: Arc, +} + +impl IsTrueExpr { + /// Create new not expression + pub fn new(arg: Arc) -> Self { + Self { arg } + } + + /// Get the input expression + pub fn arg(&self) -> &Arc { + &self.arg + } +} + +impl std::fmt::Display for IsTrueExpr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} IS TRUE", self.arg) + } +} + +impl PhysicalExpr for IsTrueExpr { + /// Return a reference to Any that can be used for downcasting + fn as_any(&self) -> &dyn Any { + self + } + + fn data_type(&self, _input_schema: &Schema) -> Result { + Ok(DataType::Boolean) + } + + fn nullable(&self, _input_schema: &Schema) -> Result { + Ok(false) + } + + fn evaluate(&self, batch: &RecordBatch) -> Result { + let arg = self.arg.evaluate(batch)?; + match arg { + ColumnarValue::Array(array) => { + match array.as_any().downcast_ref::() { + Some(bool_array) => { + let array_len = array.len(); + let mut result_builder = BooleanBuilder::new(array_len); + for i in 0..array_len { + result_builder.append_value(!bool_array.is_null(i) && bool_array.value(i)); + } + Ok(ColumnarValue::Array(Arc::new(result_builder.finish()))) + } + _ => Err(DataFusionError::Execution(format!("Cannot apply 'IS TRUE' to arguments of type '<{:?}> IS TRUE'. Supported form(s): ' IS TRUE'", array.data_type()))) + } + } + ColumnarValue::Scalar(scalar) => Ok(ColumnarValue::Scalar( + ScalarValue::Boolean(Some(scalar.is_true()?)), + )), + } + } +} + +/// Create an IS TRUE expression +pub fn is_true(arg: Arc) -> Result> { + Ok(Arc::new(IsTrueExpr::new(arg))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::expressions::col; + use arrow::{array::BooleanArray, datatypes::*, record_batch::RecordBatch}; + use std::sync::Arc; + + #[test] + fn is_true_op() -> Result<()> { + let schema = Schema::new(vec![Field::new("a", DataType::Boolean, true)]); + let a = BooleanArray::from(vec![Some(true), Some(false), None]); + let expr = is_true(col("a", &schema)?).unwrap(); + let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)])?; + + // expression: "a is true" + let result = expr.evaluate(&batch)?.into_array(batch.num_rows()); + let result = result + .as_any() + .downcast_ref::() + .expect("failed to downcast to BooleanArray"); + + let expected = &BooleanArray::from(vec![true, false, false]); + + assert_eq!(expected, result); + + Ok(()) + } +} diff --git a/datafusion/physical-expr/src/expressions/mod.rs b/datafusion/physical-expr/src/expressions/mod.rs index 00bf6aafade46..60279f46eb24d 100644 --- a/datafusion/physical-expr/src/expressions/mod.rs +++ b/datafusion/physical-expr/src/expressions/mod.rs @@ -26,8 +26,10 @@ mod datetime; mod delta; mod get_indexed_field; mod in_list; +mod is_false; mod is_not_null; mod is_null; +mod is_true; mod literal; mod negative; mod not; @@ -76,8 +78,10 @@ pub use column::{col, Column}; pub use datetime::DateTimeIntervalExpr; pub use get_indexed_field::GetIndexedFieldExpr; pub use in_list::{in_list, InListExpr}; +pub use is_false::{is_false, IsFalseExpr}; pub use is_not_null::{is_not_null, IsNotNullExpr}; pub use is_null::{is_null, IsNullExpr}; +pub use is_true::{is_true, IsTrueExpr}; pub use literal::{lit, Literal}; pub use negative::{negative, NegativeExpr}; pub use not::{not, NotExpr}; diff --git a/datafusion/physical-expr/src/planner.rs b/datafusion/physical-expr/src/planner.rs index 44fe0595ac58a..6dd8efd98aa97 100644 --- a/datafusion/physical-expr/src/planner.rs +++ b/datafusion/physical-expr/src/planner.rs @@ -195,6 +195,18 @@ pub fn create_physical_expr( input_schema, execution_props, )?), + Expr::IsTrue(expr) => expressions::is_true(create_physical_expr( + expr, + input_dfschema, + input_schema, + execution_props, + )?), + Expr::IsFalse(expr) => expressions::is_false(create_physical_expr( + expr, + input_dfschema, + input_schema, + execution_props, + )?), Expr::IsNotNull(expr) => expressions::is_not_null(create_physical_expr( expr, input_dfschema, diff --git a/datafusion/proto/proto/datafusion.proto b/datafusion/proto/proto/datafusion.proto index 7b08e4f404568..887b3977db0b5 100644 --- a/datafusion/proto/proto/datafusion.proto +++ b/datafusion/proto/proto/datafusion.proto @@ -312,6 +312,10 @@ message LogicalExprNode { CubeNode cube = 23; RollupNode rollup = 24; + + // boolean operations + IsTrue is_true_expr = 25; + IsFalse is_false_expr = 26; } } @@ -346,6 +350,14 @@ message IsNotNull { LogicalExprNode expr = 1; } +message IsTrue { + LogicalExprNode expr = 1; +} + +message IsFalse { + LogicalExprNode expr = 1; +} + message Not { LogicalExprNode expr = 1; } diff --git a/datafusion/proto/src/from_proto.rs b/datafusion/proto/src/from_proto.rs index 97b21c4cdcbb0..27f1da4c7dae0 100644 --- a/datafusion/proto/src/from_proto.rs +++ b/datafusion/proto/src/from_proto.rs @@ -905,6 +905,14 @@ pub fn parse_expr( ExprType::IsNotNullExpr(is_not_null) => Ok(Expr::IsNotNull(Box::new( parse_required_expr(&is_not_null.expr, registry, "expr")?, ))), + ExprType::IsTrueExpr(is_true) => Ok(Expr::IsTrue(Box::new(parse_required_expr( + &is_true.expr, + registry, + "expr", + )?))), + ExprType::IsFalseExpr(is_false) => Ok(Expr::IsFalse(Box::new( + parse_required_expr(&is_false.expr, registry, "expr")?, + ))), ExprType::NotExpr(not) => Ok(Expr::Not(Box::new(parse_required_expr( ¬.expr, registry, "expr", )?))), diff --git a/datafusion/proto/src/to_proto.rs b/datafusion/proto/src/to_proto.rs index a022769dcab19..04e6785349fec 100644 --- a/datafusion/proto/src/to_proto.rs +++ b/datafusion/proto/src/to_proto.rs @@ -616,6 +616,22 @@ impl TryFrom<&Expr> for protobuf::LogicalExprNode { expr_type: Some(ExprType::IsNotNullExpr(expr)), } } + Expr::IsTrue(expr) => { + let expr = Box::new(protobuf::IsTrue { + expr: Some(Box::new(expr.as_ref().try_into()?)), + }); + Self { + expr_type: Some(ExprType::IsTrueExpr(expr)), + } + } + Expr::IsFalse(expr) => { + let expr = Box::new(protobuf::IsFalse { + expr: Some(Box::new(expr.as_ref().try_into()?)), + }); + Self { + expr_type: Some(ExprType::IsFalseExpr(expr)), + } + } Expr::Between { expr, negated, diff --git a/datafusion/sql/src/planner.rs b/datafusion/sql/src/planner.rs index aaab12e267f66..79c4dc93ec688 100644 --- a/datafusion/sql/src/planner.rs +++ b/datafusion/sql/src/planner.rs @@ -1882,6 +1882,14 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { self.sql_expr_to_logical_expr(*expr, schema, ctes)?, ))), + SQLExpr::IsTrue(expr) => Ok(Expr::IsTrue(Box::new( + self.sql_expr_to_logical_expr(*expr, schema, ctes)?, + ))), + + SQLExpr::IsFalse(expr) => Ok(Expr::IsFalse(Box::new( + self.sql_expr_to_logical_expr(*expr, schema, ctes)?, + ))), + SQLExpr::IsNotNull(expr) => Ok(Expr::IsNotNull(Box::new( self.sql_expr_to_logical_expr(*expr, schema, ctes)?, ))), diff --git a/datafusion/sql/src/utils.rs b/datafusion/sql/src/utils.rs index 81ea34de187b5..01064ea2cb4d4 100644 --- a/datafusion/sql/src/utils.rs +++ b/datafusion/sql/src/utils.rs @@ -280,6 +280,12 @@ where nested_expr, replacement_fn, )?))), + Expr::IsTrue(nested_expr) => Ok(Expr::IsTrue(Box::new( + clone_with_replacement(nested_expr, replacement_fn)?, + ))), + Expr::IsFalse(nested_expr) => Ok(Expr::IsFalse(Box::new( + clone_with_replacement(nested_expr, replacement_fn)?, + ))), Expr::IsNotNull(nested_expr) => Ok(Expr::IsNotNull(Box::new( clone_with_replacement(nested_expr, replacement_fn)?, ))),