Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support "IS TRUE/FALSE" syntax #3189

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from 13 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
18 changes: 18 additions & 0 deletions datafusion/common/src/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,24 @@ impl ScalarValue {
}
}

/// whether this value is true or not.
pub fn is_true(&self) -> Result<bool> {
match self {
ScalarValue::Boolean(Some(true)) => Ok(true),
ScalarValue::Null => Ok(false),
e => Err(DataFusionError::Execution(format!("Cannot apply 'IS TRUE' to arguments of type '<{}> IS TRUE'. Supported form(s): '<BOOLEAN> IS TRUE'", e.get_datatype())))
}
}

/// whether this value is false or not.
pub fn is_false(&self) -> Result<bool> {
match self {
ScalarValue::Boolean(Some(false)) => Ok(true),
ScalarValue::Null => Ok(false),
e => Err(DataFusionError::Execution(format!("Cannot apply 'IS FALSE' to arguments of type '<{}> IS FALSE'. Supported form(s): '<BOOLEAN> 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)
Expand Down
2 changes: 2 additions & 0 deletions datafusion/core/src/datasource/listing/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ impl ExpressionVisitor for ApplicabilityVisitor<'_> {
| Expr::Alias(_, _)
| Expr::ScalarVariable(_, _)
| Expr::Not(_)
| Expr::IsTrue(_)
| Expr::IsFalse(_)
| Expr::IsNotNull(_)
| Expr::IsNull(_)
| Expr::Negative(_)
Expand Down
8 changes: 8 additions & 0 deletions datafusion/core/src/physical_plan/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ fn create_physical_name(e: &Expr, is_first_expr: bool) -> Result<String> {
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))
Expand Down
28 changes: 28 additions & 0 deletions datafusion/expr/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ pub enum Expr {
},
/// Negation of an expression. The expression's type must be a boolean to make sense.
Not(Box<Expr>),
/// Whether an expression is true. This expression is never null.
IsTrue(Box<Expr>),
/// Whether an expression is false. This expression is never null.
IsFalse(Box<Expr>),
/// Whether an expression is not Null. This expression is never null.
IsNotNull(Box<Expr>),
/// Whether an expression is Null. This expression is never null.
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -795,6 +815,14 @@ fn create_name(e: &Expr, input_schema: &DFSchema) -> Result<String> {
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))
Expand Down
2 changes: 2 additions & 0 deletions datafusion/expr/src/expr_rewriter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?),
Expand Down
8 changes: 7 additions & 1 deletion datafusion/expr/src/expr_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Expr::InSubquery { expr, .. } => expr.nullable(input_schema),
Expr::ScalarSubquery(subquery) => {
Ok(subquery.subquery.schema().field(0).is_nullable())
Expand Down
2 changes: 2 additions & 0 deletions datafusion/expr/src/expr_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions datafusion/expr/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ impl ExpressionVisitor for ColumnNameVisitor<'_> {
| Expr::Literal(_)
| Expr::BinaryExpr { .. }
| Expr::Not(_)
| Expr::IsTrue(_)
| Expr::IsFalse(_)
| Expr::IsNotNull(_)
| Expr::IsNull(_)
| Expr::Negative(_)
Expand Down
6 changes: 6 additions & 0 deletions datafusion/optimizer/src/common_subexpr_eliminate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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-");
}
Expand Down
2 changes: 2 additions & 0 deletions datafusion/optimizer/src/simplify_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,8 @@ impl<'a> ConstEvaluator<'a> {
Expr::Literal(_)
| Expr::BinaryExpr { .. }
| Expr::Not(_)
| Expr::IsTrue(_)
| Expr::IsFalse(_)
| Expr::IsNotNull(_)
| Expr::IsNull(_)
| Expr::Negative(_)
Expand Down
139 changes: 139 additions & 0 deletions datafusion/physical-expr/src/expressions/is_false.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// 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},
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<dyn PhysicalExpr>,
}

impl IsFalseExpr {
/// Create new not expression
pub fn new(arg: Arc<dyn PhysicalExpr>) -> Self {
Self { arg }
}

/// Get the input expression
pub fn arg(&self) -> &Arc<dyn PhysicalExpr> {
&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<DataType> {
Ok(DataType::Boolean)
}

fn nullable(&self, _input_schema: &Schema) -> Result<bool> {
Ok(false)
}

fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
let arg = self.arg.evaluate(batch)?;
match arg {
ColumnarValue::Array(array) => {
let array_len = array.len();
let my_array = ColumnarValue::Array(array).into_array(array_len);
let true_array = BooleanArray::from(vec![Some(true)]);
let false_array = BooleanArray::from(vec![Some(false)]);
let null_array = BooleanArray::from(vec![None]);
let mut result_vec = vec![];
for i in 0..array_len {
let current = (*my_array).slice(i, 1);
if (*current).eq(&false_array) {
result_vec.push(Some(true));
} else if (*current).eq(&true_array) || (*current).eq(&null_array) {
result_vec.push(Some(false));
} else {
return Err(DataFusionError::Execution(format!("Cannot apply 'IS FALSE' to arguments of type '<{:?}> IS FALSE'. Supported form(s): '<BOOLEAN> IS FALSE'", current.data_type())))
}
}

let return_array = BooleanArray::from(result_vec);
Ok(ColumnarValue::Array(Arc::new(return_array)))
}
ColumnarValue::Scalar(scalar) => Ok(ColumnarValue::Scalar(
ScalarValue::Boolean(Some(scalar.is_false().unwrap())),
)),
}
}
}

/// Create an IS FALSE expression
pub fn is_false(arg: Arc<dyn PhysicalExpr>) -> Result<Arc<dyn PhysicalExpr>> {
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::<BooleanArray>()
.expect("failed to downcast to BooleanArray");

let expected = &BooleanArray::from(vec![false, true, false]);

assert_eq!(expected, result);

Ok(())
}
}
Loading