Jefffrey commented on code in PR #10909:
URL: https://github.com/apache/arrow-rs/pull/10909#discussion_r3889376536


##########
arrow-select/src/take.rs:
##########
@@ -384,6 +392,40 @@ fn take_impl<IndexType: ArrowPrimitiveType, const CHECKED: 
bool>(
     }
 }
 
+/// Takes union type ids, substituting a valid type id for null take indices.
+///
+/// Union arrays do not have a top-level null bitmap. A null is represented by 
selecting an
+/// arbitrary valid child type id with a null value in that child. In 
particular, a null index
+/// cannot fall back to type id `0`, as unions are not required to have such a 
child.
+fn take_union_type_ids<IndexType: ArrowPrimitiveType>(
+    fields: &UnionFields,
+    type_ids: &ScalarBuffer<i8>,
+    indices: &PrimitiveArray<IndexType>,
+) -> Result<ScalarBuffer<i8>, ArrowError> {
+    let taken_type_ids = take_native(type_ids, indices);
+    if indices.null_count() == 0 {
+        return Ok(taken_type_ids);
+    }
+
+    let null_type_id = fields
+        .iter()
+        .next()
+        .map(|(type_id, _)| type_id)
+        .ok_or_else(|| ArrowError::ComputeError("Cannot take from an empty 
union".into()))?;

Review Comment:
   ```suggestion
           .ok_or_else(|| ArrowError::ComputeError("Cannot take from a union 
with zero fields when indices contains nulls".into()))?;
   ```
   
   this is an interesting edge case, nice spot; could we add a unit test for 
this? for both this case, union with 0 fields and indices has a null, and when 
union with 0 fields but indices has no null (should still be valid, albeit 
degenerate case)



##########
arrow-select/src/take.rs:
##########
@@ -384,6 +392,40 @@ fn take_impl<IndexType: ArrowPrimitiveType, const CHECKED: 
bool>(
     }
 }
 
+/// Takes union type ids, substituting a valid type id for null take indices.
+///
+/// Union arrays do not have a top-level null bitmap. A null is represented by 
selecting an
+/// arbitrary valid child type id with a null value in that child. In 
particular, a null index
+/// cannot fall back to type id `0`, as unions are not required to have such a 
child.
+fn take_union_type_ids<IndexType: ArrowPrimitiveType>(
+    fields: &UnionFields,
+    type_ids: &ScalarBuffer<i8>,
+    indices: &PrimitiveArray<IndexType>,
+) -> Result<ScalarBuffer<i8>, ArrowError> {
+    let taken_type_ids = take_native(type_ids, indices);
+    if indices.null_count() == 0 {
+        return Ok(taken_type_ids);
+    }
+
+    let null_type_id = fields

Review Comment:
   actually i thought of another potential edge case: the field must be 
nullable as well 🤔
   
   we can probably do this in a followup, as perhaps it might be a wider issue



##########
arrow-select/src/take.rs:
##########
@@ -2787,6 +2910,22 @@ mod tests {
         assert_eq!(take_out_values.values(), &[2, 1]);
     }
 
+    #[test]
+    fn test_take_runs_null_indices() {

Review Comment:
   oh theres another separate issue i just thought of: if the input run array 
has non-null for its values, but we take with indices that has nulls, then 
we're creating a run array with non-null values (in its field definition) but 
actually has nulls...
   
   i think we can deal with it in a followup, as i suspect we might have 
similar issues with other kernels (interleave in particular) and i dont want to 
scope creep this PR too much
   
   for reference this is an example unit test
   
   ```rust
   
   #[test]
   fn test123() {
       let array = unsafe {
           RunArray::<Int16Type>::new_unchecked(
               DataType::RunEndEncoded(
                   Field::new("run_ends", DataType::Int16, false).into(),
                   Field::new("values", DataType::Int32, false).into(),
               ),
               RunEndBuffer::new(vec![1, 2].into(), 0, 2),
               Arc::new(Int32Array::from(vec![1, 2])),
           )
       };
       dbg!(&array);
       let indices = Int32Array::from(vec![Some(0), None, Some(1)]);
       let taken = take(&array, &indices, None).unwrap();
       dbg!(&taken);
       println!("{}", array.data_type());
       println!("{}", taken.data_type());
   }
   ```
   
   and the output:
   
   ```sh
   [arrow-select/src/take.rs:2941:9] &array = RunArray {run_ends: [1, 2], 
values: PrimitiveArray<Int32>
   [
     1,
     2,
   ]}
   
   [arrow-select/src/take.rs:2944:9] &taken = RunArray {run_ends: [1, 2, 3], 
values: PrimitiveArray<Int32>
   [
     1,
     null,
     2,
   ]}
   
   RunEndEncoded("run_ends": non-null Int16, "values": non-null Int32)
   RunEndEncoded("run_ends": non-null Int16, "values": non-null Int32)
   ```
   
   - so input & output array has identical datatype, down to the nullability of 
its values; however can see the actual values array has nulls



##########
arrow-select/src/take.rs:
##########
@@ -1127,6 +1181,75 @@ fn take_run<T: RunEndIndexType, I: ArrowPrimitiveType>(
     )
 }
 
+/// Physical run index for each logical take slot.
+///
+/// `None` means the logical index is null. Only valid indices are passed to
+/// [`RunArray::get_physical_indices`]; a null slot's backing integer is 
ignored
+/// and may be out of range.
+fn physical_indices_for_take<T: RunEndIndexType, I: ArrowPrimitiveType>(
+    run_array: &RunArray<T>,
+    logical_indices: &PrimitiveArray<I>,
+) -> Result<Vec<Option<usize>>, ArrowError> {
+    if logical_indices.null_count() == 0 {
+        return Ok(run_array
+            .get_physical_indices(logical_indices.values())?
+            .into_iter()
+            .map(Some)
+            .collect());
+    }
+
+    let valid_logical: Vec<_> = logical_indices.iter().flatten().collect();
+
+    let valid_physical = if valid_logical.is_empty() {
+        Vec::new()
+    } else {
+        run_array.get_physical_indices(&valid_logical)?
+    };
+
+    let mut valid_physical = valid_physical.into_iter();
+    Ok(logical_indices
+        .iter()
+        .map(|index| index.map(|_| valid_physical.next().unwrap()))
+        .collect())
+}
+
+fn is_new_run_take(
+    values: &dyn Array,
+    prev_idx: Option<usize>,
+    cur_idx: Option<usize>,
+    values_cmp: &arrow_cmp::DynComparator,
+) -> bool {
+    let prev_valid = prev_idx.is_some_and(|idx| values.is_valid(idx));
+    let cur_valid = cur_idx.is_some_and(|idx| values.is_valid(idx));
+    match (prev_valid, cur_valid) {
+        (false, false) => false,
+        (true, true) => {
+            let prev = prev_idx.unwrap();
+            let cur = cur_idx.unwrap();
+            prev != cur && values_cmp(cur, prev).is_ne()
+        }
+        _ => true,
+    }
+}
+
+fn push_run_take_index<I: ArrowPrimitiveType>(
+    take_value_indices: &mut Vec<I::Native>,
+    take_value_is_valid: &mut NullBufferBuilder,
+    values: &dyn Array,
+    physical: Option<usize>,
+) {
+    match physical.filter(|&idx| values.is_valid(idx)) {
+        Some(idx) => {
+            take_value_indices.push(I::Native::from_usize(idx).unwrap());
+            take_value_is_valid.append_non_null();
+        }
+        None => {
+            take_value_indices.push(I::Native::default());
+            take_value_is_valid.append_null();
+        }
+    }

Review Comment:
   ```suggestion
       // Safe unwrap since physical indices came from a valid run array
       let index = I::Native::from_usize(physical.unwrap_or_default()).unwrap();
       take_value_indices.push(index);
       let is_valid = physical.is_some_and(|idx| values.is_valid(idx));
       take_value_is_valid.append(is_valid);
   ```
   
   we could shorten like so
   
   and then preferably inline this function, since its only used twice and the 
signature is a bit gnarly (4 arguments, 2 of which are mutable references, just 
for 5 lines)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to