Skip to content

Commit b55d0ed

Browse files
guojidanjonahgao
andauthored
feature: support nvl(ifnull) function (#9284)
* feature: support nvl(ifnull) function * add sqllogictest * add docs entry * Update docs/source/user-guide/sql/scalar_functions.md Co-authored-by: Jonah Gao <[email protected]> * fix some code * fix docs --------- Co-authored-by: Jonah Gao <[email protected]>
1 parent a26f583 commit b55d0ed

File tree

4 files changed

+422
-1
lines changed

4 files changed

+422
-1
lines changed

datafusion/functions/src/core/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
//! "core" DataFusion functions
1919
2020
mod nullif;
21+
mod nvl;
2122

2223
// create UDFs
2324
make_udf_function!(nullif::NullIfFunc, NULLIF, nullif);
25+
make_udf_function!(nvl::NVLFunc, NVL, nvl);
2426

2527
// Export the functions out of this package, both as expr_fn as well as a list of functions
2628
export_functions!(
27-
(nullif, arg_1 arg_2, "returns NULL if value1 equals value2; otherwise it returns value1. This can be used to perform the inverse operation of the COALESCE expression.")
29+
(nullif, arg_1 arg_2, "returns NULL if value1 equals value2; otherwise it returns value1. This can be used to perform the inverse operation of the COALESCE expression."),
30+
(nvl, arg_1 arg_2, "returns value2 if value1 is NULL; otherwise it returns value1")
2831
);
2932

datafusion/functions/src/core/nvl.rs

+277
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use arrow::datatypes::DataType;
19+
use datafusion_common::{internal_err, Result, DataFusionError};
20+
use datafusion_expr::{ColumnarValue, ScalarUDFImpl, Signature, Volatility};
21+
use arrow::compute::kernels::zip::zip;
22+
use arrow::compute::is_not_null;
23+
use arrow::array::Array;
24+
25+
#[derive(Debug)]
26+
pub(super) struct NVLFunc {
27+
signature: Signature,
28+
aliases: Vec<String>,
29+
}
30+
31+
/// Currently supported types by the nvl/ifnull function.
32+
/// The order of these types correspond to the order on which coercion applies
33+
/// This should thus be from least informative to most informative
34+
static SUPPORTED_NVL_TYPES: &[DataType] = &[
35+
DataType::Boolean,
36+
DataType::UInt8,
37+
DataType::UInt16,
38+
DataType::UInt32,
39+
DataType::UInt64,
40+
DataType::Int8,
41+
DataType::Int16,
42+
DataType::Int32,
43+
DataType::Int64,
44+
DataType::Float32,
45+
DataType::Float64,
46+
DataType::Utf8,
47+
DataType::LargeUtf8,
48+
];
49+
50+
impl NVLFunc {
51+
pub fn new() -> Self {
52+
Self {
53+
signature:
54+
Signature::uniform(2, SUPPORTED_NVL_TYPES.to_vec(),
55+
Volatility::Immutable,
56+
),
57+
aliases: vec![String::from("ifnull")],
58+
}
59+
}
60+
}
61+
62+
impl ScalarUDFImpl for NVLFunc {
63+
fn as_any(&self) -> &dyn std::any::Any {
64+
self
65+
}
66+
67+
fn name(&self) -> &str {
68+
"nvl"
69+
}
70+
71+
fn signature(&self) -> &Signature {
72+
&self.signature
73+
}
74+
75+
fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
76+
// NVL has two args and they might get coerced, get a preview of this
77+
let coerced_types = datafusion_expr::type_coercion::functions::data_types(arg_types, &self.signature);
78+
coerced_types.map(|typs| typs[0].clone())
79+
.map_err(|e| e.context("Failed to coerce arguments for NVL")
80+
)
81+
}
82+
83+
fn invoke(&self, args: &[ColumnarValue]) -> Result<ColumnarValue> {
84+
nvl_func(args)
85+
}
86+
87+
fn aliases(&self) -> &[String] {
88+
&self.aliases
89+
}
90+
}
91+
92+
fn nvl_func(args: &[ColumnarValue]) -> Result<ColumnarValue> {
93+
if args.len() != 2 {
94+
return internal_err!(
95+
"{:?} args were supplied but NVL/IFNULL takes exactly two args",
96+
args.len()
97+
);
98+
}
99+
let (lhs_array, rhs_array) = match (&args[0], &args[1]) {
100+
(ColumnarValue::Array(lhs), ColumnarValue::Scalar(rhs)) => {
101+
(lhs.clone(), rhs.to_array_of_size(lhs.len())?)
102+
}
103+
(ColumnarValue::Array(lhs), ColumnarValue::Array(rhs)) => {
104+
(lhs.clone(), rhs.clone())
105+
}
106+
(ColumnarValue::Scalar(lhs), ColumnarValue::Array(rhs)) => {
107+
(lhs.to_array_of_size(rhs.len())?, rhs.clone())
108+
}
109+
(ColumnarValue::Scalar(lhs), ColumnarValue::Scalar(rhs)) => {
110+
let mut current_value = lhs;
111+
if lhs.is_null() {
112+
current_value = rhs;
113+
}
114+
return Ok(ColumnarValue::Scalar(current_value.clone()));
115+
}
116+
};
117+
let to_apply = is_not_null(&lhs_array)?;
118+
let value = zip(&to_apply, &lhs_array, &rhs_array)?;
119+
Ok(ColumnarValue::Array(value))
120+
}
121+
122+
#[cfg(test)]
123+
mod tests {
124+
use std::sync::Arc;
125+
126+
use arrow::array::*;
127+
128+
use super::*;
129+
use datafusion_common::{Result, ScalarValue};
130+
131+
#[test]
132+
fn nvl_int32() -> Result<()> {
133+
let a = Int32Array::from(vec![
134+
Some(1),
135+
Some(2),
136+
None,
137+
None,
138+
Some(3),
139+
None,
140+
None,
141+
Some(4),
142+
Some(5),
143+
]);
144+
let a = ColumnarValue::Array(Arc::new(a));
145+
146+
let lit_array = ColumnarValue::Scalar(ScalarValue::Int32(Some(6i32)));
147+
148+
let result = nvl_func(&[a, lit_array])?;
149+
let result = result.into_array(0).expect("Failed to convert to array");
150+
151+
let expected = Arc::new(Int32Array::from(vec![
152+
Some(1),
153+
Some(2),
154+
Some(6),
155+
Some(6),
156+
Some(3),
157+
Some(6),
158+
Some(6),
159+
Some(4),
160+
Some(5),
161+
])) as ArrayRef;
162+
assert_eq!(expected.as_ref(), result.as_ref());
163+
Ok(())
164+
}
165+
166+
#[test]
167+
// Ensure that arrays with no nulls can also invoke nvl() correctly
168+
fn nvl_int32_nonulls() -> Result<()> {
169+
let a = Int32Array::from(vec![1, 3, 10, 7, 8, 1, 2, 4, 5]);
170+
let a = ColumnarValue::Array(Arc::new(a));
171+
172+
let lit_array = ColumnarValue::Scalar(ScalarValue::Int32(Some(20i32)));
173+
174+
let result = nvl_func(&[a, lit_array])?;
175+
let result = result.into_array(0).expect("Failed to convert to array");
176+
177+
let expected = Arc::new(Int32Array::from(vec![
178+
Some(1),
179+
Some(3),
180+
Some(10),
181+
Some(7),
182+
Some(8),
183+
Some(1),
184+
Some(2),
185+
Some(4),
186+
Some(5),
187+
])) as ArrayRef;
188+
assert_eq!(expected.as_ref(), result.as_ref());
189+
Ok(())
190+
}
191+
192+
#[test]
193+
fn nvl_boolean() -> Result<()> {
194+
let a = BooleanArray::from(vec![Some(true), Some(false), None]);
195+
let a = ColumnarValue::Array(Arc::new(a));
196+
197+
let lit_array = ColumnarValue::Scalar(ScalarValue::Boolean(Some(false)));
198+
199+
let result = nvl_func(&[a, lit_array])?;
200+
let result = result.into_array(0).expect("Failed to convert to array");
201+
202+
let expected =
203+
Arc::new(BooleanArray::from(vec![Some(true), Some(false), Some(false)])) as ArrayRef;
204+
205+
assert_eq!(expected.as_ref(), result.as_ref());
206+
Ok(())
207+
}
208+
209+
#[test]
210+
fn nvl_string() -> Result<()> {
211+
let a = StringArray::from(vec![Some("foo"), Some("bar"), None, Some("baz")]);
212+
let a = ColumnarValue::Array(Arc::new(a));
213+
214+
let lit_array = ColumnarValue::Scalar(ScalarValue::from("bax"));
215+
216+
let result = nvl_func(&[a, lit_array])?;
217+
let result = result.into_array(0).expect("Failed to convert to array");
218+
219+
let expected = Arc::new(StringArray::from(vec![
220+
Some("foo"),
221+
Some("bar"),
222+
Some("bax"),
223+
Some("baz"),
224+
])) as ArrayRef;
225+
226+
assert_eq!(expected.as_ref(), result.as_ref());
227+
Ok(())
228+
}
229+
230+
#[test]
231+
fn nvl_literal_first() -> Result<()> {
232+
let a = Int32Array::from(vec![Some(1), Some(2), None, None, Some(3), Some(4)]);
233+
let a = ColumnarValue::Array(Arc::new(a));
234+
235+
let lit_array = ColumnarValue::Scalar(ScalarValue::Int32(Some(2i32)));
236+
237+
let result = nvl_func(&[lit_array, a])?;
238+
let result = result.into_array(0).expect("Failed to convert to array");
239+
240+
let expected = Arc::new(Int32Array::from(vec![
241+
Some(2),
242+
Some(2),
243+
Some(2),
244+
Some(2),
245+
Some(2),
246+
Some(2),
247+
])) as ArrayRef;
248+
assert_eq!(expected.as_ref(), result.as_ref());
249+
Ok(())
250+
}
251+
252+
#[test]
253+
fn nvl_scalar() -> Result<()> {
254+
let a_null = ColumnarValue::Scalar(ScalarValue::Int32(None));
255+
let b_null = ColumnarValue::Scalar(ScalarValue::Int32(Some(2i32)));
256+
257+
let result_null = nvl_func(&[a_null, b_null])?;
258+
let result_null = result_null.into_array(1).expect("Failed to convert to array");
259+
260+
let expected_null = Arc::new(Int32Array::from(vec![Some(2i32)])) as ArrayRef;
261+
262+
assert_eq!(expected_null.as_ref(), result_null.as_ref());
263+
264+
let a_nnull = ColumnarValue::Scalar(ScalarValue::Int32(Some(2i32)));
265+
let b_nnull = ColumnarValue::Scalar(ScalarValue::Int32(Some(1i32)));
266+
267+
let result_nnull = nvl_func(&[a_nnull, b_nnull])?;
268+
let result_nnull = result_nnull
269+
.into_array(1)
270+
.expect("Failed to convert to array");
271+
272+
let expected_nnull = Arc::new(Int32Array::from(vec![Some(2i32)])) as ArrayRef;
273+
assert_eq!(expected_nnull.as_ref(), result_nnull.as_ref());
274+
275+
Ok(())
276+
}
277+
}

0 commit comments

Comments
 (0)