use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use arrow_array::{
Array, ArrayRef, Int64Array, RecordBatch, RecordBatchOptions, UInt64Array, new_empty_array,
};
use arrow_row::{RowConverter, Rows, SortField};
use arrow_schema::{ArrowError, DataType, Field, Schema};
use arrow_select::concat::concat;
use arrow_select::interleave::interleave;
use arrow_select::take::take;
use rayon::prelude::*;
use runlength::How;
use crate::error::{Error, Result};
use crate::kernels::Kernel;
use crate::rle::{ArrowRle, default_fill};
#[derive(Clone, Debug)]
pub struct ArrowRleBatch {
keys: RecordBatch,
offsets: Vec<usize>,
ends: Vec<i64>,
values: ArrayRef,
}
pub(crate) fn single_key_column(name: &str, column: ArrayRef) -> RecordBatch {
let schema = Schema::new(vec![Field::new(
name,
column.data_type().clone(),
column.is_nullable(),
)]);
RecordBatch::try_new(Arc::new(schema), vec![column]).expect("one column matches its own schema")
}
pub(crate) fn key_rows(converter: &RowConverter, keys: &RecordBatch) -> Result<Rows> {
Ok(converter.convert_columns(keys.columns())?)
}
pub(crate) fn key_converter(keys: &RecordBatch) -> Result<RowConverter> {
let fields = keys
.schema()
.fields()
.iter()
.map(|f| SortField::new(f.data_type().clone()))
.collect();
Ok(RowConverter::new(fields)?)
}
fn concat_values(rles: &[ArrowRle], data_type: &DataType) -> Result<ArrayRef> {
if rles.is_empty() {
return Ok(new_empty_array(data_type));
}
let arrays: Vec<&dyn Array> = rles.iter().map(|r| r.values.as_ref()).collect();
Ok(concat(&arrays)?)
}
impl ArrowRleBatch {
pub fn try_new(
keys: RecordBatch,
offsets: Vec<usize>,
ends: Vec<i64>,
values: ArrayRef,
) -> Result<Self> {
let n = keys.num_rows();
if offsets.len() != n + 1 {
return Err(runlength::Error::LengthMismatch {
what: "offsets and keys + 1",
left: offsets.len(),
right: n + 1,
}
.into());
}
if ends.len() != values.len() {
return Err(runlength::Error::LengthMismatch {
what: "ends and values",
left: ends.len(),
right: values.len(),
}
.into());
}
if offsets[0] != 0 || offsets[n] != ends.len() || offsets.windows(2).any(|w| w[0] > w[1]) {
return Err(runlength::Error::InvalidOffsets.into());
}
for (position, w) in offsets.windows(2).enumerate() {
let rle_ends = ends[w[0]..w[1]].to_vec();
let values = values.slice(w[0], w[1] - w[0]);
ArrowRle::try_new(rle_ends, values).map_err(|e| e.in_key(position))?;
}
let converter = key_converter(&keys)?;
let rows = key_rows(&converter, &keys)?;
let mut seen = HashSet::with_capacity(n);
for (position, row) in rows.iter().enumerate() {
if !seen.insert(row) {
return Err(runlength::Error::DuplicateKey { position }.into());
}
}
Ok(ArrowRleBatch {
keys,
offsets,
ends,
values,
})
}
pub(crate) fn from_parts_unchecked(
keys: RecordBatch,
offsets: Vec<usize>,
ends: Vec<i64>,
values: ArrayRef,
) -> Result<Self> {
debug_assert_eq!(offsets.len(), keys.num_rows() + 1);
Ok(ArrowRleBatch {
keys,
offsets,
ends,
values,
})
}
pub fn from_rles(keys: RecordBatch, rles: Vec<ArrowRle>, data_type: &DataType) -> Result<Self> {
if keys.num_rows() != rles.len() {
return Err(runlength::Error::LengthMismatch {
what: "keys and sequences",
left: keys.num_rows(),
right: rles.len(),
}
.into());
}
let mut offsets = Vec::with_capacity(rles.len() + 1);
offsets.push(0);
let mut ends = Vec::new();
for rle in &rles {
ends.extend_from_slice(&rle.ends);
offsets.push(ends.len());
}
let values = concat_values(&rles, data_type)?;
Self::try_new(keys, offsets, ends, values)
}
pub fn len(&self) -> usize {
self.keys.num_rows()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn keys(&self) -> &RecordBatch {
&self.keys
}
pub fn offsets(&self) -> &[usize] {
&self.offsets
}
pub fn ends(&self) -> &[i64] {
&self.ends
}
pub fn values(&self) -> &ArrayRef {
&self.values
}
pub fn get_index(&self, position: usize) -> Option<ArrowRle> {
(position < self.len()).then(|| self.rle_at(position))
}
fn rle_at(&self, position: usize) -> ArrowRle {
let (start, end) = (self.offsets[position], self.offsets[position + 1]);
ArrowRle {
ends: self.ends[start..end].to_vec(),
values: self.values.slice(start, end - start),
}
}
pub fn iter(&self) -> impl Iterator<Item = ArrowRle> + '_ {
(0..self.len()).map(|i| self.rle_at(i))
}
pub fn positions(&self, keys: &RecordBatch) -> Result<Vec<Option<usize>>> {
self.check_key_schema(keys)?;
let converter = key_converter(&self.keys)?;
let mine = key_rows(&converter, &self.keys)?;
let index: HashMap<_, _> = mine.iter().enumerate().map(|(i, row)| (row, i)).collect();
let theirs = key_rows(&converter, keys)?;
Ok(theirs.iter().map(|row| index.get(&row).copied()).collect())
}
pub(crate) fn check_key_schema(&self, keys: &RecordBatch) -> Result<()> {
let (mine, theirs) = (self.keys.schema(), keys.schema());
let same = mine.fields().len() == theirs.fields().len()
&& mine
.fields()
.iter()
.zip(theirs.fields())
.all(|(a, b)| a.name() == b.name() && a.data_type() == b.data_type());
if same {
Ok(())
} else {
Err(Error::KeySchemaMismatch)
}
}
#[allow(clippy::type_complexity)]
fn join(
&self,
other: &ArrowRleBatch,
how: How,
) -> Result<(Vec<(usize, usize)>, Vec<(Option<usize>, Option<usize>)>)> {
self.check_key_schema(&other.keys)?;
let converter = key_converter(&self.keys)?;
let (rows_a, rows_b) = (
key_rows(&converter, &self.keys)?,
key_rows(&converter, &other.keys)?,
);
let index_a: HashMap<_, _> = rows_a.iter().enumerate().map(|(i, row)| (row, i)).collect();
let index_b: HashMap<_, _> = rows_b.iter().enumerate().map(|(j, row)| (row, j)).collect();
let (mut sources, mut pairs) = (Vec::new(), Vec::new());
for (i, row) in rows_a.iter().enumerate() {
let j = index_b.get(&row).copied();
if j.is_some() || matches!(how, How::Outer | How::Left) {
sources.push((0, i));
pairs.push((Some(i), j));
}
}
if matches!(how, How::Outer | How::Right) {
for (j, row) in rows_b.iter().enumerate() {
if !index_a.contains_key(&row) {
sources.push((1, j));
pairs.push((None, Some(j)));
}
}
}
Ok((sources, pairs))
}
pub fn zip_with<F>(
&self,
other: &ArrowRleBatch,
how: How,
fill_a: Option<&dyn Array>,
fill_b: Option<&dyn Array>,
f: F,
) -> Result<ArrowRleBatch>
where
F: Fn(&dyn Array, &dyn Array) -> std::result::Result<ArrayRef, ArrowError> + Sync,
{
let (sources, pairs) = self.join(other, how)?;
let rles = pairs
.par_iter()
.enumerate()
.map(|(position, &(i, j))| {
let a = i.map_or_else(
|| ArrowRle::empty(self.values.data_type()),
|i| self.rle_at(i),
);
let b = j.map_or_else(
|| ArrowRle::empty(other.values.data_type()),
|j| other.rle_at(j),
);
a.zip_with(&b, fill_a, fill_b, &f)
.map_err(|e| e.in_key(position))
})
.collect::<Result<Vec<_>>>()?;
let columns = (0..self.keys.num_columns())
.map(|c| {
interleave(
&[self.keys.column(c).as_ref(), other.keys.column(c).as_ref()],
&sources,
)
})
.collect::<std::result::Result<Vec<_>, _>>()?;
let keys = RecordBatch::try_new_with_options(
self.keys.schema(),
columns,
&RecordBatchOptions::new().with_row_count(Some(sources.len())),
)?;
let data_type = rles
.first()
.map_or(DataType::Null, |r| r.data_type().clone());
let mut offsets = Vec::with_capacity(rles.len() + 1);
offsets.push(0);
let mut ends = Vec::new();
for rle in &rles {
ends.extend_from_slice(&rle.ends);
offsets.push(ends.len());
}
let values = concat_values(&rles, &data_type)?;
Self::from_parts_unchecked(keys, offsets, ends, values)
}
pub fn to_record_batch(&self) -> Result<RecordBatch> {
let mut key_index = Vec::with_capacity(self.ends.len());
let mut starts = Vec::with_capacity(self.ends.len());
for (k, w) in self.offsets.windows(2).enumerate() {
let mut prev = 0;
for &end in &self.ends[w[0]..w[1]] {
key_index.push(k as u64);
starts.push(prev);
prev = end;
}
}
let key_index = UInt64Array::from(key_index);
let mut fields: Vec<Field> = self
.keys
.schema()
.fields()
.iter()
.map(|f| f.as_ref().clone())
.collect();
let mut columns = self
.keys
.columns()
.iter()
.map(|c| take(c.as_ref(), &key_index, None))
.collect::<std::result::Result<Vec<_>, _>>()?;
fields.push(Field::new("start", DataType::Int64, false));
fields.push(Field::new("end", DataType::Int64, false));
fields.push(Field::new("value", self.values.data_type().clone(), true));
columns.push(Arc::new(Int64Array::from(starts)));
columns.push(Arc::new(Int64Array::from(self.ends.clone())));
columns.push(self.values.clone());
Ok(RecordBatch::try_new_with_options(
Arc::new(Schema::new(fields)),
columns,
&RecordBatchOptions::new().with_row_count(Some(self.ends.len())),
)?)
}
pub fn zip_with_default(&self, other: &ArrowRleBatch, f: Kernel) -> Result<ArrowRleBatch> {
self.op(other, f)
}
fn op(&self, other: &ArrowRleBatch, f: Kernel) -> Result<ArrowRleBatch> {
let fill_a = default_fill(self.values.data_type());
let fill_b = default_fill(other.values.data_type());
self.zip_with(other, How::Outer, fill_a.as_deref(), fill_b.as_deref(), f)
}
}
crate::kernels::kernel_methods!(
ArrowRleBatch,
" Keys are outer-joined and shorter sides padded with [`default_fill`]."
);
impl PartialEq for ArrowRleBatch {
fn eq(&self, other: &Self) -> bool {
self.keys == other.keys && self.iter().zip(other.iter()).all(|(a, b)| a == b)
}
}