use crate::algebra::ReindexPacker;
use crate::repr::Batch;
use crate::schema::{oob_col, ColumnLocator, SchemaColumn, SchemaDescriptor, SchemaFacts, TypeCode};
use gnitz_expr::OrderLocator;
use gnitz_wire::OrderKey;
use gnitz_wire::PkBuf;
use gnitz_wire::RowSource;
use crate::algebra::{
append_image, has_fixed_image, image_slot_col, int16_image, scalar_image, write_image_slot, ImageKind, WideKind,
IMAGE_COL,
};
const RANK_COL: SchemaColumn = SchemaColumn::new(TypeCode::U8, false);
struct OrderSpec {
key: OrderLocator,
kind: ImageKind,
nullable: bool,
}
impl OrderSpec {
#[inline]
fn append_image(&self, src: &impl RowSource, row: usize, out: &mut Vec<u8>) {
if self.nullable {
let is_null = self.key.loc.is_null(src, row);
out.push((is_null != self.key.nulls_first) as u8);
if is_null {
return;
}
}
append_image(&self.key.loc, self.kind, self.key.desc, src, row, out);
}
#[inline]
fn write_lead(&self, src: &impl RowSource, row: usize, lead: &mut [u8], image: &mut Vec<u8>) {
let slot = match self.nullable {
false => lead,
true => {
let is_null = self.key.loc.is_null(src, row);
let (rank, slot) = lead.split_at_mut(1);
rank[0] = (is_null != self.key.nulls_first) as u8;
if is_null {
slot.fill(0);
return;
}
slot
}
};
let (loc, desc) = (&self.key.loc, self.key.desc);
match self.kind {
ImageKind::Scalar(kind) => slot.copy_from_slice(&scalar_image(loc, kind, desc, src, row).to_be_bytes()),
ImageKind::Wide(WideKind::Fixed(_)) => slot.copy_from_slice(&int16_image(loc, desc, src, row)),
ImageKind::Wide(WideKind::Bytes) => {
append_image(loc, self.kind, desc, src, row, image);
write_image_slot(slot, image);
}
}
}
fn lead_cols(&self) -> impl Iterator<Item = SchemaColumn> {
let slot = image_slot_col(matches!(self.kind, ImageKind::Wide(_)));
self.nullable.then_some(RANK_COL).into_iter().chain([slot])
}
fn fits_lead(&self) -> bool {
has_fixed_image(self.kind)
}
}
pub struct TopNIndex {
key_packer: ReindexPacker,
pub schema: SchemaDescriptor,
lead: OrderSpec,
rest: Vec<OrderSpec>,
pub(super) carried_in_index: Vec<ColumnLocator>,
lead_bytes: usize,
}
impl TopNIndex {
pub(super) fn new(
input: &SchemaDescriptor,
group_cols: &[u32],
order: &[OrderKey],
output: &SchemaDescriptor,
) -> Result<Self, String> {
let mut order = order.iter().map(|key| {
let loc = input
.try_locate(key.col as usize)
.ok_or_else(|| oob_col("top-n: order column", key.col as u32, input))?;
Ok(OrderSpec {
key: OrderLocator::of(loc, key),
kind: ImageKind::of(loc.type_code()),
nullable: input.columns[key.col as usize].nullable,
})
});
let lead = order.next().ok_or_else(|| "top-n: no order keys".to_string())??;
let rest = order.collect::<Result<Vec<_>, String>>()?;
let suffix: Vec<SchemaColumn> = lead.lead_cols().collect();
let (key_packer, mut b) = ReindexPacker::new_group_key(input, group_cols, &suffix)?;
for _ in 0..rest.len() + usize::from(!lead.fits_lead()) {
b.push(IMAGE_COL);
}
b.push_payload_of(output);
let schema = b.finish().map_err(|e| format!("top-n: index {e}"))?;
let tail = schema.num_columns() - output.num_payload_cols();
Ok(TopNIndex {
key_packer,
lead,
rest,
carried_in_index: (tail..schema.num_columns()).map(|c| schema.locate(c)).collect(),
schema,
lead_bytes: suffix.iter().map(|c| c.size() as usize).sum(),
})
}
#[inline]
pub(super) fn group_prefix<'a, R: RowSource>(&self, buf: &'a mut [u8], src: &R, row: usize) -> &'a [u8] {
self.key_packer.pack_prefix(buf, src, row)
}
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 batch(&self, delta: &Batch, carried: &[ColumnLocator]) -> Batch {
let mb = delta.as_mem_batch();
let mut out = Batch::with_capacity_blob(&self.schema, delta.count.max(1), delta.blob().len());
let stride = self.key_packer.out_stride;
let mut image = Vec::new();
self.key_packer.for_each_key(&mb, stride + self.lead_bytes, |row, key| {
let weight = mb.get_weight(row);
if weight == 0 {
return;
}
image.clear();
self.lead
.write_lead(&mb, row, &mut key[stride..stride + self.lead_bytes], &mut image);
out.begin_row(key, weight);
let mut col = 0;
if !self.lead.fits_lead() {
out.extend_col_blob(col, &image);
col += 1;
}
for spec in &self.rest {
image.clear();
spec.append_image(&mb, row, &mut image);
out.extend_col_blob(col, &image);
col += 1;
}
out.append_cells_from(col, carried, &mb, row);
out.commit_row();
});
out
}
}