Compare on nested type List, Struct, and so on
(
op: Operator,
lhs: &dyn Datum,
rhs: &dyn Datum,
)
| 133 | |
| 134 | /// Compare on nested type List, Struct, and so on |
| 135 | pub fn compare_op_for_nested( |
| 136 | op: Operator, |
| 137 | lhs: &dyn Datum, |
| 138 | rhs: &dyn Datum, |
| 139 | ) -> Result<BooleanArray> { |
| 140 | let (l, is_l_scalar) = lhs.get(); |
| 141 | let (r, is_r_scalar) = rhs.get(); |
| 142 | let l_len = l.len(); |
| 143 | let r_len = r.len(); |
| 144 | |
| 145 | assert_or_internal_err!(l_len == r_len || is_l_scalar || is_r_scalar, "len mismatch"); |
| 146 | |
| 147 | let len = match is_l_scalar { |
| 148 | true => r_len, |
| 149 | false => l_len, |
| 150 | }; |
| 151 | |
| 152 | // fast path, if compare with one null and operator is not 'distinct', then we can return null array directly |
| 153 | if !matches!(op, Operator::IsDistinctFrom | Operator::IsNotDistinctFrom) |
| 154 | && (is_l_scalar && l.null_count() == 1 || is_r_scalar && r.null_count() == 1) |
| 155 | { |
| 156 | return Ok(BooleanArray::new_null(len)); |
| 157 | } |
| 158 | |
| 159 | // TODO: make SortOptions configurable |
| 160 | // we choose the default behaviour from arrow-rs which has null-first that follow spark's behaviour |
| 161 | let cmp = make_comparator(l, r, SortOptions::default())?; |
| 162 | |
| 163 | let cmp_with_op = |i, j| match op { |
| 164 | Operator::Eq | Operator::IsNotDistinctFrom => cmp(i, j).is_eq(), |
| 165 | Operator::Lt => cmp(i, j).is_lt(), |
| 166 | Operator::Gt => cmp(i, j).is_gt(), |
| 167 | Operator::LtEq => !cmp(i, j).is_gt(), |
| 168 | Operator::GtEq => !cmp(i, j).is_lt(), |
| 169 | Operator::NotEq | Operator::IsDistinctFrom => !cmp(i, j).is_eq(), |
| 170 | _ => unreachable!("unexpected operator found"), |
| 171 | }; |
| 172 | |
| 173 | let values = match (is_l_scalar, is_r_scalar) { |
| 174 | (false, false) => BooleanBuffer::collect_bool(len, |i| cmp_with_op(i, i)), |
| 175 | (true, false) => BooleanBuffer::collect_bool(len, |i| cmp_with_op(0, i)), |
| 176 | (false, true) => BooleanBuffer::collect_bool(len, |i| cmp_with_op(i, 0)), |
| 177 | (true, true) => std::iter::once(cmp_with_op(0, 0)).collect(), |
| 178 | }; |
| 179 | |
| 180 | // Distinct understand how to compare with NULL |
| 181 | // i.e NULL is distinct from NULL -> false |
| 182 | if matches!(op, Operator::IsDistinctFrom | Operator::IsNotDistinctFrom) { |
| 183 | Ok(BooleanArray::new(values, None)) |
| 184 | } else { |
| 185 | // If one of the side is NULL, we return NULL |
| 186 | // i.e. NULL eq NULL -> NULL |
| 187 | // For nested comparisons, we need to ensure the null buffer matches the result length |
| 188 | let nulls = match (is_l_scalar, is_r_scalar) { |
| 189 | (false, false) | (true, true) => NullBuffer::union(l.nulls(), r.nulls()), |
| 190 | (true, false) => { |
| 191 | // When left is null-scalar and right is array, expand left nulls to match result length |
| 192 | match l.nulls().filter(|nulls| nulls.is_null(0)) { |
no test coverage detected
searching dependent graphs…