From 578dd8f87a2897fa586e5833fead18cc24d1912a Mon Sep 17 00:00:00 2001 From: Kould Date: Thu, 22 Feb 2024 20:41:25 +0800 Subject: [PATCH] feat: add isnull function (#3360) * code fmt * feat: add isnull function * feat: add isnull function --- src/common/function/src/function_registry.rs | 2 + src/common/function/src/scalars/expression.rs | 14 +++ .../src/scalars/expression/is_null.rs | 109 ++++++++++++++++++ .../common/function/expression.result | 70 +++++++++++ .../standalone/common/function/expression.sql | 19 +++ 5 files changed, 214 insertions(+) create mode 100644 src/common/function/src/scalars/expression/is_null.rs create mode 100644 tests/cases/standalone/common/function/expression.result create mode 100644 tests/cases/standalone/common/function/expression.sql diff --git a/src/common/function/src/function_registry.rs b/src/common/function/src/function_registry.rs index ef6fb67de6a6..04eb673b56ce 100644 --- a/src/common/function/src/function_registry.rs +++ b/src/common/function/src/function_registry.rs @@ -21,6 +21,7 @@ use once_cell::sync::Lazy; use crate::function::FunctionRef; use crate::scalars::aggregate::{AggregateFunctionMetaRef, AggregateFunctions}; use crate::scalars::date::DateFunction; +use crate::scalars::expression::ExpressionFunction; use crate::scalars::math::MathFunction; use crate::scalars::numpy::NumpyFunction; use crate::scalars::timestamp::TimestampFunction; @@ -80,6 +81,7 @@ pub static FUNCTION_REGISTRY: Lazy> = Lazy::new(|| { NumpyFunction::register(&function_registry); TimestampFunction::register(&function_registry); DateFunction::register(&function_registry); + ExpressionFunction::register(&function_registry); // Aggregate functions AggregateFunctions::register(&function_registry); diff --git a/src/common/function/src/scalars/expression.rs b/src/common/function/src/scalars/expression.rs index 363d9acb6475..573202f6e282 100644 --- a/src/common/function/src/scalars/expression.rs +++ b/src/common/function/src/scalars/expression.rs @@ -14,8 +14,22 @@ mod binary; mod ctx; +mod is_null; mod unary; +use std::sync::Arc; + pub use binary::scalar_binary_op; pub use ctx::EvalContext; pub use unary::scalar_unary_op; + +use crate::function_registry::FunctionRegistry; +use crate::scalars::expression::is_null::IsNullFunction; + +pub(crate) struct ExpressionFunction; + +impl ExpressionFunction { + pub fn register(registry: &FunctionRegistry) { + registry.register(Arc::new(IsNullFunction)); + } +} diff --git a/src/common/function/src/scalars/expression/is_null.rs b/src/common/function/src/scalars/expression/is_null.rs new file mode 100644 index 000000000000..ecae98cf87a6 --- /dev/null +++ b/src/common/function/src/scalars/expression/is_null.rs @@ -0,0 +1,109 @@ +// Copyright 2023 Greptime Team +// +// Licensed 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. + +use std::fmt; +use std::fmt::Display; +use std::sync::Arc; + +use common_query::error; +use common_query::error::{ArrowComputeSnafu, InvalidFuncArgsSnafu}; +use common_query::prelude::{Signature, Volatility}; +use datafusion::arrow::array::ArrayRef; +use datafusion::arrow::compute::is_null; +use datatypes::data_type::ConcreteDataType; +use datatypes::prelude::VectorRef; +use datatypes::vectors::Helper; +use snafu::{ensure, ResultExt}; + +use crate::function::{Function, FunctionContext}; + +const NAME: &str = "isnull"; + +/// The function to check whether an expression is NULL +#[derive(Clone, Debug, Default)] +pub struct IsNullFunction; + +impl Display for IsNullFunction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", NAME.to_ascii_uppercase()) + } +} + +impl Function for IsNullFunction { + fn name(&self) -> &str { + NAME + } + + fn return_type(&self, _: &[ConcreteDataType]) -> common_query::error::Result { + Ok(ConcreteDataType::boolean_datatype()) + } + + fn signature(&self) -> Signature { + Signature::any(1, Volatility::Immutable) + } + + fn eval( + &self, + _func_ctx: FunctionContext, + columns: &[VectorRef], + ) -> common_query::error::Result { + ensure!( + columns.len() == 1, + InvalidFuncArgsSnafu { + err_msg: format!( + "The length of the args is not correct, expect exactly one, have: {}", + columns.len() + ), + } + ); + let values = &columns[0]; + let arrow_array = &values.to_arrow_array(); + let result = is_null(arrow_array).context(ArrowComputeSnafu)?; + + Helper::try_into_vector(Arc::new(result) as ArrayRef).context(error::FromArrowArraySnafu) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use common_query::prelude::TypeSignature; + use datatypes::scalars::ScalarVector; + use datatypes::vectors::{BooleanVector, Float32Vector}; + + use super::*; + #[test] + fn test_is_null_function() { + let is_null = IsNullFunction; + assert_eq!("isnull", is_null.name()); + assert_eq!( + ConcreteDataType::boolean_datatype(), + is_null.return_type(&[]).unwrap() + ); + assert_eq!( + is_null.signature(), + Signature { + type_signature: TypeSignature::Any(1), + volatility: Volatility::Immutable + } + ); + let values = vec![None, Some(3.0), None]; + + let args: Vec = vec![Arc::new(Float32Vector::from(values))]; + let vector = is_null.eval(FunctionContext::default(), &args).unwrap(); + let expect: VectorRef = Arc::new(BooleanVector::from_vec(vec![true, false, true])); + assert_eq!(expect, vector); + } +} diff --git a/tests/cases/standalone/common/function/expression.result b/tests/cases/standalone/common/function/expression.result new file mode 100644 index 000000000000..061b6a02dbdf --- /dev/null +++ b/tests/cases/standalone/common/function/expression.result @@ -0,0 +1,70 @@ +CREATE TABLE t(a INTEGER, ts timestamp time index); + +Affected Rows: 0 + +INSERT INTO t VALUES (1, 1), (null, 2), (3, 3); + +Affected Rows: 3 + +SELECT ISNULL(a) from t; + ++-------------+ +| isnull(t.a) | ++-------------+ +| false | +| true | +| false | ++-------------+ + +SELECT ISNULL(null); + ++--------------+ +| isnull(NULL) | ++--------------+ +| true | ++--------------+ + +SELECT ISNULL(1); + ++------------------+ +| isnull(Int64(1)) | ++------------------+ +| false | ++------------------+ + +SELECT ISNULL(-1); + ++-------------------+ +| isnull(Int64(-1)) | ++-------------------+ +| false | ++-------------------+ + +SELECT ISNULL(1.0); + ++--------------------+ +| isnull(Float64(1)) | ++--------------------+ +| false | ++--------------------+ + +SELECT ISNULL(true); + ++-----------------------+ +| isnull(Boolean(true)) | ++-----------------------+ +| false | ++-----------------------+ + +SELECT ISNULL('string'); + ++------------------------+ +| isnull(Utf8("string")) | ++------------------------+ +| false | ++------------------------+ + +DROP TABLE t; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/function/expression.sql b/tests/cases/standalone/common/function/expression.sql new file mode 100644 index 000000000000..19c5b5d4e1a5 --- /dev/null +++ b/tests/cases/standalone/common/function/expression.sql @@ -0,0 +1,19 @@ +CREATE TABLE t(a INTEGER, ts timestamp time index); + +INSERT INTO t VALUES (1, 1), (null, 2), (3, 3); + +SELECT ISNULL(a) from t; + +SELECT ISNULL(null); + +SELECT ISNULL(1); + +SELECT ISNULL(-1); + +SELECT ISNULL(1.0); + +SELECT ISNULL(true); + +SELECT ISNULL('string'); + +DROP TABLE t;