use crate::algebra::ReindexPacker;
use crate::algebra::{image_slot_col, ImageCol, IMAGE_COL};
use crate::algebra::{Agg, AggValues};
use crate::repr::{copy_runs, range_rows, Batch, ReadCursor};
use crate::schema::{SchemaColumn, SchemaDescriptor, TypeCode, MAX_PK_BYTES};
use gnitz_wire::payload_bytes;
use gnitz_wire::PkBuf;
use gnitz_wire::RowSource;
use std::ops::ControlFlow;
const ORDINAL_COL: SchemaColumn = SchemaColumn::new(TypeCode::U8, false);
const IMAGE_SLOT: usize = 0;
const ORDINAL_BYTES: usize = ORDINAL_COL.size();
const _: () = assert!(ORDINAL_BYTES == 1);
struct AviAgg {
agg_idx: u8,
image: ImageCol,
}
pub(super) struct AviBake {
key_packer: ReindexPacker,
pub(super) schema: SchemaDescriptor,
aggs: Vec<AviAgg>,
value_bytes: usize,
}
impl AviBake {
pub(super) fn new(src: &SchemaDescriptor, group_by_cols: &[u32], aggs: &[Agg]) -> Result<Option<Self>, String> {
let aggs: Vec<AviAgg> = aggs
.iter()
.enumerate()
.filter_map(|(k, agg)| {
let image = agg.index_image()?;
Some(AviAgg { agg_idx: k as u8, image })
})
.collect();
if aggs.is_empty() {
return Ok(None);
}
let has_wide = aggs.iter().any(|a| a.image.is_wide());
let has_payload = aggs.iter().any(|a| !a.image.fits_slot());
let suffix = [ORDINAL_COL, image_slot_col(has_wide)];
let (key_packer, mut b) = ReindexPacker::new_group_key(src, group_by_cols, &suffix)?;
if has_payload {
b.push(IMAGE_COL);
}
let schema = b.finish().map_err(|e| format!("min/max index: {e}"))?;
debug_assert!(!has_payload || schema.payload_slot(schema.num_columns() - 1) == Some(IMAGE_SLOT));
Ok(Some(AviBake {
schema,
key_packer,
aggs,
value_bytes: suffix[1].size(),
}))
}
#[inline]
fn prefix<'a>(&self, buf: &'a mut [u8], ord: u8) -> &'a [u8] {
buf[self.key_packer.out_stride] = ord;
&buf[..self.key_packer.out_stride + ORDINAL_BYTES]
}
pub(super) fn group_span<R: RowSource>(
&self,
src: &R,
rows: impl Iterator<Item = usize>,
) -> Option<(PkBuf, PkBuf)> {
self.key_packer.prefix_span(src, rows)
}
pub(super) fn seed_extremes<R: RowSource>(
&self,
cur: &mut ReadCursor,
src: &R,
row: usize,
aggs: &[Agg],
vals: &mut AggValues,
g: usize,
) {
let mut key = [0u8; MAX_PK_BYTES];
self.key_packer.pack_prefix(&mut key, src, row);
for (j, a) in self.aggs.iter().enumerate() {
let k = a.agg_idx as usize;
let prefix_len = self.prefix(&mut key, j as u8).len();
if !cur.for_each_positive_with_prefix_until(&key[..prefix_len], |_| ControlFlow::Break(())) {
vals.clear_extreme(k, g);
continue;
}
let image = if a.image.fits_slot() {
&cur.current_pk_bytes()[prefix_len..]
} else {
let (src, row) = cur.current_row_source();
payload_bytes(src, row, IMAGE_SLOT)
};
vals.seed_from_index(k, &aggs[k], g, image);
}
}
}
pub(super) fn avi_batch(delta: &Batch, bake: &AviBake) -> Batch {
debug_assert!(delta.is_consolidated());
let mb = delta.as_mem_batch();
let n = delta.count;
let stride = bake.key_packer.out_stride;
let value = stride + ORDINAL_BYTES..stride + ORDINAL_BYTES + bake.value_bytes;
let width = value.end;
let has_payload = bake.schema.num_payload_cols() != 0;
let mut out = Batch::with_capacity(&bake.schema, (n * bake.aggs.len()).max(1));
let (mut images, mut ends) = (Vec::new(), Vec::new());
for (j, a) in bake.aggs.iter().enumerate() {
let runs = delta.runs_without_nulls(a.image.loc.null_bit());
let rows = range_rows(&runs);
images.clear();
ends.clear();
out.append_session(rows).write(rows, |w| {
bake.key_packer.pack_rows(w.pk_mut(), width, &mb, &runs);
copy_runs::<8>(delta.weight_data(), w.weight_mut(), runs.iter().copied(), 8);
w.null_bmp_mut().fill(0);
let live = runs.iter().flat_map(|&(s, e)| s..e);
for (key, row) in w.pk_mut().chunks_exact_mut(width).zip(live) {
key[stride] = j as u8;
a.image.write_slot(&mb, row, &mut key[value.clone()], &mut images);
if !a.image.fits_slot() {
ends.push(images.len());
}
}
if has_payload {
let (cells, blob, _) = w.string_col_mut(IMAGE_SLOT);
if a.image.fits_slot() {
cells.fill(0);
}
let mut start = 0;
for (cell, &end) in cells.as_chunks_mut::<16>().0.iter_mut().zip(&ends) {
*cell = gnitz_wire::encode_german_string(&images[start..end], blob);
start = end;
}
}
});
}
out
}