blob: 35a7b8835b148e988c8ac584bde39d84dbbeae74 [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 std::{any::Any, ops::Range, sync::Arc};
use arrow::{array::*, record_batch::RecordBatch};
use datafusion::{common::Result, physical_expr::PhysicalExprRef};
use datafusion_ext_commons::{
arrow::coalesce::coalesce_arrays_unchecked, batch_size, downcast_any,
};
use crate::generate::{GenerateState, GeneratedRows, Generator};
fn append_range(ranges: &mut Vec<Range<usize>>, range: Range<usize>) {
if range.is_empty() {
return;
}
if let Some(last) = ranges.last_mut()
&& last.end == range.start
{
last.end = range.end;
return;
}
ranges.push(range);
}
fn slices_for_ranges(values: &ArrayRef, ranges: &[Range<usize>]) -> Vec<ArrayRef> {
ranges
.iter()
.map(|range| values.slice(range.start, range.len()))
.collect()
}
#[derive(Debug)]
pub struct ExplodeArray {
child: PhysicalExprRef,
position: bool,
}
impl ExplodeArray {
pub fn new(child: PhysicalExprRef, position: bool) -> Self {
Self { child, position }
}
}
impl Generator for ExplodeArray {
fn exprs(&self) -> Vec<PhysicalExprRef> {
vec![self.child.clone()]
}
fn with_new_exprs(&self, exprs: Vec<PhysicalExprRef>) -> Result<Arc<dyn Generator>> {
Ok(Arc::new(Self {
child: exprs[0].clone(),
position: self.position,
}))
}
fn eval_start(&self, batch: &RecordBatch) -> Result<Box<dyn GenerateState>> {
let input_array = self.child.evaluate(batch)?.into_array(batch.num_rows())?;
Ok(Box::new(ExplodeArrayGenerateState {
input_array: input_array.as_list().clone(),
cur_row_id: 0,
}))
}
fn eval_loop(&self, state: &mut Box<dyn GenerateState>) -> Result<Option<GeneratedRows>> {
let state = downcast_any!(state, mut ExplodeArrayGenerateState)?;
let batch_size = batch_size();
let mut row_idx = state.cur_row_id;
let mut row_ids = vec![];
let mut pos_ids = vec![];
let mut sub_list_ranges = vec![];
let value_offsets = state.input_array.value_offsets();
while row_idx < state.input_array.len() && row_ids.len() < batch_size {
if state.input_array.is_null(row_idx) {
row_idx += 1;
continue;
}
let start = value_offsets[row_idx] as usize;
let end = value_offsets[row_idx + 1] as usize;
let len = end - start;
row_ids.resize(row_ids.len() + len, row_idx as i32);
if self.position {
pos_ids.extend(0..len as i32);
}
append_range(&mut sub_list_ranges, start..end);
row_idx += 1;
}
state.cur_row_id = row_idx;
if row_ids.is_empty() {
return Ok(None);
}
let sub_lists = slices_for_ranges(state.input_array.values(), &sub_list_ranges);
let values = coalesce_arrays_unchecked(&state.input_array.value_type(), &sub_lists);
let cols = if self.position {
vec![Arc::new(Int32Array::from(pos_ids)), values]
} else {
vec![values]
};
Ok(Some(GeneratedRows {
row_ids: Int32Array::from(row_ids),
cols,
}))
}
}
struct ExplodeArrayGenerateState {
pub input_array: ListArray,
pub cur_row_id: usize,
}
impl GenerateState for ExplodeArrayGenerateState {
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn cur_row_id(&self) -> usize {
self.cur_row_id
}
}
#[derive(Debug)]
pub struct ExplodeMap {
child: PhysicalExprRef,
position: bool,
}
impl ExplodeMap {
pub fn new(child: PhysicalExprRef, position: bool) -> Self {
Self { child, position }
}
}
impl Generator for ExplodeMap {
fn exprs(&self) -> Vec<PhysicalExprRef> {
vec![self.child.clone()]
}
fn with_new_exprs(&self, exprs: Vec<PhysicalExprRef>) -> Result<Arc<dyn Generator>> {
Ok(Arc::new(Self {
child: exprs[0].clone(),
position: self.position,
}))
}
fn eval_start(&self, batch: &RecordBatch) -> Result<Box<dyn GenerateState>> {
let input_array = self.child.evaluate(batch)?.into_array(batch.num_rows())?;
Ok(Box::new(ExplodeMapGenerateState {
input_array: input_array.as_map().clone(),
cur_row_id: 0,
}))
}
fn eval_loop(&self, state: &mut Box<dyn GenerateState>) -> Result<Option<GeneratedRows>> {
let state = downcast_any!(state, mut ExplodeMapGenerateState)?;
let batch_size = batch_size();
let mut row_idx = state.cur_row_id;
let mut row_ids = vec![];
let mut pos_ids = vec![];
let mut entry_ranges = vec![];
let value_offsets = state.input_array.value_offsets();
while row_idx < state.input_array.len() && row_ids.len() < batch_size {
if state.input_array.is_null(row_idx) {
row_idx += 1;
continue;
}
let start = value_offsets[row_idx] as usize;
let end = value_offsets[row_idx + 1] as usize;
let len = end - start;
row_ids.resize(row_ids.len() + len, row_idx as i32);
if self.position {
pos_ids.extend(0..len as i32);
}
append_range(&mut entry_ranges, start..end);
row_idx += 1;
}
state.cur_row_id = row_idx;
if row_ids.is_empty() {
return Ok(None);
}
let sub_keys = slices_for_ranges(state.input_array.keys(), &entry_ranges);
let sub_vals = slices_for_ranges(state.input_array.values(), &entry_ranges);
let keys = coalesce_arrays_unchecked(&state.input_array.key_type(), &sub_keys);
let vals = coalesce_arrays_unchecked(&state.input_array.value_type(), &sub_vals);
let cols = if self.position {
vec![Arc::new(Int32Array::from(pos_ids)), keys, vals]
} else {
vec![keys, vals]
};
Ok(Some(GeneratedRows {
row_ids: Int32Array::from(row_ids),
cols,
}))
}
}
struct ExplodeMapGenerateState {
pub input_array: MapArray,
pub cur_row_id: usize,
}
impl GenerateState for ExplodeMapGenerateState {
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn cur_row_id(&self) -> usize {
self.cur_row_id
}
}
#[cfg(test)]
mod tests {
use arrow::{
array::{Array, ArrayRef, Int32Array, ListArray, MapArray, StringArray},
buffer::{NullBuffer, OffsetBuffer},
datatypes::{DataType, Field, Int32Type},
record_batch::RecordBatch,
};
use datafusion::physical_expr::expressions::Column;
use super::*;
#[test]
fn dense_map_reuses_input_key_and_value_buffers() -> Result<()> {
let map = MapArray::new_from_strings(
["a", "b", "c", "d"].into_iter(),
&Int32Array::from_iter_values([1, 2, 3, 4]),
&[0, 2, 4],
)?;
let input_key_data = map.keys().as_string::<i32>().value_data().as_ptr();
let input_values = map.values().as_primitive::<Int32Type>().values().clone();
let batch = RecordBatch::try_from_iter([("map", Arc::new(map) as ArrayRef)])?;
let generator = ExplodeMap::new(Arc::new(Column::new("map", 0)), false);
let mut state = generator.eval_start(&batch)?;
let output = generator
.eval_loop(&mut state)?
.expect("dense map should generate rows");
assert_eq!(output.cols.len(), 2);
let output_key_data = output.cols[0].as_string::<i32>().value_data().as_ptr();
let output_values = output.cols[1].as_primitive::<Int32Type>().values();
assert_eq!(input_key_data, output_key_data);
assert!(input_values.ptr_eq(output_values));
Ok(())
}
#[test]
fn posexplode_list_outputs_positions() -> Result<()> {
let list = ListArray::from_iter_primitive::<Int32Type, _, _>([
Some(vec![Some(1), Some(2)]),
Some(vec![Some(3)]),
]);
let batch = RecordBatch::try_from_iter([("list", Arc::new(list) as ArrayRef)])?;
let generator = ExplodeArray::new(Arc::new(Column::new("list", 0)), true);
let mut state = generator.eval_start(&batch)?;
let output = generator
.eval_loop(&mut state)?
.expect("non-empty list should generate rows");
assert_eq!(output.cols.len(), 2);
assert_eq!(
output.cols[0].as_primitive::<Int32Type>(),
&Int32Array::from_iter_values([0, 1, 0])
);
assert_eq!(
output.cols[1].as_primitive::<Int32Type>(),
&Int32Array::from_iter_values([1, 2, 3])
);
Ok(())
}
#[test]
fn list_offsets_skip_hidden_null_values() -> Result<()> {
let field = Arc::new(Field::new_list_field(DataType::Int32, true));
let offsets = OffsetBuffer::new(vec![0_i32, 1, 3, 4, 6].into());
let values = Arc::new(Int32Array::from_iter_values([0, 10, 11, 20, 30, 31]));
let nulls = NullBuffer::from(vec![true, false, true, true]);
let list = ListArray::try_new(field, offsets, values, Some(nulls))?.slice(0, 4);
let batch = RecordBatch::try_from_iter([("list", Arc::new(list) as ArrayRef)])?;
let generator = ExplodeArray::new(Arc::new(Column::new("list", 0)), false);
let mut state = generator.eval_start(&batch)?;
let output = generator
.eval_loop(&mut state)?
.expect("valid list rows should generate rows");
assert_eq!(
output.cols[0].as_primitive::<Int32Type>(),
&Int32Array::from_iter_values([0, 20, 30, 31])
);
Ok(())
}
#[test]
fn map_offsets_skip_hidden_null_entries() -> Result<()> {
let map = MapArray::new_from_strings(
["a", "hidden1", "hidden2", "d"].into_iter(),
&Int32Array::from_iter_values([1, 10, 11, 4]),
&[0, 1, 3, 4],
)?;
let (field, offsets, entries, _, ordered) = map.into_parts();
let map = MapArray::try_new(
field,
offsets,
entries,
Some(NullBuffer::from(vec![true, false, true])),
ordered,
)?;
let batch = RecordBatch::try_from_iter([("map", Arc::new(map) as ArrayRef)])?;
let generator = ExplodeMap::new(Arc::new(Column::new("map", 0)), false);
let mut state = generator.eval_start(&batch)?;
let output = generator
.eval_loop(&mut state)?
.expect("valid map rows should generate rows");
assert_eq!(
output.cols[0].as_string::<i32>(),
&StringArray::from(vec!["a", "d"])
);
assert_eq!(
output.cols[1].as_primitive::<Int32Type>(),
&Int32Array::from_iter_values([1, 4])
);
Ok(())
}
#[test]
fn sliced_list_parent_reuses_input_value_buffer() -> Result<()> {
let list = ListArray::from_iter_primitive::<Int32Type, _, _>([
Some(vec![Some(0), Some(1)]),
Some(vec![Some(2)]),
Some(vec![Some(3), Some(4)]),
])
.slice(1, 2);
let input_values = list.values().as_primitive::<Int32Type>().values().clone();
let batch = RecordBatch::try_from_iter([("list", Arc::new(list) as ArrayRef)])?;
let generator = ExplodeArray::new(Arc::new(Column::new("list", 0)), false);
let mut state = generator.eval_start(&batch)?;
let output = generator
.eval_loop(&mut state)?
.expect("sliced list should generate rows");
assert_eq!(output.cols.len(), 1);
let output_values = output.cols[0].as_primitive::<Int32Type>();
assert_eq!(output_values, &Int32Array::from_iter_values([2, 3, 4]));
assert_eq!(
input_values.inner().data_ptr(),
output_values.values().inner().data_ptr()
);
Ok(())
}
}