blob: 7084bc440e86bf01ccf80f990aa3ee0f0eb581bb [file]
// 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.
use arrow::array::BooleanArray;
use arrow::array::{make_comparator, ArrayRef, Datum};
use arrow::buffer::NullBuffer;
use arrow::compute::SortOptions;
use arrow::error::ArrowError;
use datafusion_common::DataFusionError;
use datafusion_common::{arrow_datafusion_err, internal_err};
use datafusion_common::{Result, ScalarValue};
use datafusion_expr_common::columnar_value::ColumnarValue;
use datafusion_expr_common::operator::Operator;
use std::sync::Arc;
/// Applies a binary [`Datum`] kernel `f` to `lhs` and `rhs`
///
/// This maps arrow-rs' [`Datum`] kernels to DataFusion's [`ColumnarValue`] abstraction
pub fn apply(
lhs: &ColumnarValue,
rhs: &ColumnarValue,
f: impl Fn(&dyn Datum, &dyn Datum) -> Result<ArrayRef, ArrowError>,
) -> Result<ColumnarValue> {
match (&lhs, &rhs) {
(ColumnarValue::Array(left), ColumnarValue::Array(right)) => {
Ok(ColumnarValue::Array(f(&left.as_ref(), &right.as_ref())?))
}
(ColumnarValue::Scalar(left), ColumnarValue::Array(right)) => Ok(
ColumnarValue::Array(f(&left.to_scalar()?, &right.as_ref())?),
),
(ColumnarValue::Array(left), ColumnarValue::Scalar(right)) => Ok(
ColumnarValue::Array(f(&left.as_ref(), &right.to_scalar()?)?),
),
(ColumnarValue::Scalar(left), ColumnarValue::Scalar(right)) => {
let array = f(&left.to_scalar()?, &right.to_scalar()?)?;
let scalar = ScalarValue::try_from_array(array.as_ref(), 0)?;
Ok(ColumnarValue::Scalar(scalar))
}
}
}
/// Applies a binary [`Datum`] comparison kernel `f` to `lhs` and `rhs`
pub fn apply_cmp(
lhs: &ColumnarValue,
rhs: &ColumnarValue,
f: impl Fn(&dyn Datum, &dyn Datum) -> Result<BooleanArray, ArrowError>,
) -> Result<ColumnarValue> {
apply(lhs, rhs, |l, r| Ok(Arc::new(f(l, r)?)))
}
/// Applies a binary [`Datum`] comparison kernel `f` to `lhs` and `rhs` for nested type like
/// List, FixedSizeList, LargeList, Struct, Union, Map, or a dictionary of a nested type
pub fn apply_cmp_for_nested(
op: Operator,
lhs: &ColumnarValue,
rhs: &ColumnarValue,
) -> Result<ColumnarValue> {
if matches!(
op,
Operator::Eq
| Operator::NotEq
| Operator::Lt
| Operator::Gt
| Operator::LtEq
| Operator::GtEq
| Operator::IsDistinctFrom
| Operator::IsNotDistinctFrom
) {
apply(lhs, rhs, |l, r| {
Ok(Arc::new(compare_op_for_nested(op, l, r)?))
})
} else {
internal_err!("invalid operator for nested")
}
}
/// Compare with eq with either nested or non-nested
pub fn compare_with_eq(
lhs: &dyn Datum,
rhs: &dyn Datum,
is_nested: bool,
) -> Result<BooleanArray> {
if is_nested {
compare_op_for_nested(Operator::Eq, lhs, rhs)
} else {
arrow::compute::kernels::cmp::eq(lhs, rhs).map_err(|e| arrow_datafusion_err!(e))
}
}
/// Compare on nested type List, Struct, and so on
pub fn compare_op_for_nested(
op: Operator,
lhs: &dyn Datum,
rhs: &dyn Datum,
) -> Result<BooleanArray> {
let (l, is_l_scalar) = lhs.get();
let (r, is_r_scalar) = rhs.get();
let l_len = l.len();
let r_len = r.len();
if l_len != r_len && !is_l_scalar && !is_r_scalar {
return internal_err!("len mismatch");
}
let len = match is_l_scalar {
true => r_len,
false => l_len,
};
// fast path, if compare with one null and operator is not 'distinct', then we can return null array directly
if !matches!(op, Operator::IsDistinctFrom | Operator::IsNotDistinctFrom)
&& (is_l_scalar && l.null_count() == 1 || is_r_scalar && r.null_count() == 1)
{
return Ok(BooleanArray::new_null(len));
}
// TODO: make SortOptions configurable
// we choose the default behaviour from arrow-rs which has null-first that follow spark's behaviour
let cmp = make_comparator(l, r, SortOptions::default())?;
let cmp_with_op = |i, j| match op {
Operator::Eq | Operator::IsNotDistinctFrom => cmp(i, j).is_eq(),
Operator::Lt => cmp(i, j).is_lt(),
Operator::Gt => cmp(i, j).is_gt(),
Operator::LtEq => !cmp(i, j).is_gt(),
Operator::GtEq => !cmp(i, j).is_lt(),
Operator::NotEq | Operator::IsDistinctFrom => !cmp(i, j).is_eq(),
_ => unreachable!("unexpected operator found"),
};
let values = match (is_l_scalar, is_r_scalar) {
(false, false) => (0..len).map(|i| cmp_with_op(i, i)).collect(),
(true, false) => (0..len).map(|i| cmp_with_op(0, i)).collect(),
(false, true) => (0..len).map(|i| cmp_with_op(i, 0)).collect(),
(true, true) => std::iter::once(cmp_with_op(0, 0)).collect(),
};
// Distinct understand how to compare with NULL
// i.e NULL is distinct from NULL -> false
if matches!(op, Operator::IsDistinctFrom | Operator::IsNotDistinctFrom) {
Ok(BooleanArray::new(values, None))
} else {
// If one of the side is NULL, we return NULL
// i.e. NULL eq NULL -> NULL
// For nested comparisons, we need to ensure the null buffer matches the result length
let nulls = match (is_l_scalar, is_r_scalar) {
(false, false) | (true, true) => NullBuffer::union(l.nulls(), r.nulls()),
(true, false) => {
// When left is null-scalar and right is array, expand left nulls to match result length
match l.nulls().filter(|nulls| !nulls.is_valid(0)) {
Some(_) => Some(NullBuffer::new_null(len)), // Left scalar is null
None => r.nulls().cloned(), // Left scalar is non-null
}
}
(false, true) => {
// When right is null-scalar and left is array, expand right nulls to match result length
match r.nulls().filter(|nulls| !nulls.is_valid(0)) {
Some(_) => Some(NullBuffer::new_null(len)), // Right scalar is null
None => l.nulls().cloned(), // Right scalar is non-null
}
}
};
Ok(BooleanArray::new(values, nulls))
}
}