use alloc::vec::Vec;
use super::projection::{self, ProjectedField, ProjectedRow};
use super::{ColumnarScan, MISSING_BATCH_COLUMN, merge::COL_WHOLE_VALUE};
use crate::blob_tree::field_row::{CELL_REFS_COLUMN, RowCell, RowField, decode_row};
use crate::blob_tree::handle::BlobIndirection;
use crate::coding::Decode;
use crate::table::columnar::{
COL_USER_KEY, COL_VALUE_TYPE, Column, ColumnBatch, bytes_column_row, cell_refs,
frame_bytes_column,
};
use crate::table::columnar_predicate::{PredicateApply, take_column};
use crate::{Error, Slice, ValueType};
pub(super) struct CellSource {
pub(super) version: crate::version::SuperVersion,
pub(super) source: crate::blob_tree::BlobSource,
}
struct Fetch {
row: usize,
field: Option<usize>,
indirection: BlobIndirection,
}
struct Rows {
keys: Vec<Slice>,
cells: Vec<Vec<Option<Slice>>>,
whole: Vec<(usize, Slice)>,
}
pub(super) fn materialize(
scan: &ColumnarScan,
cells: &CellSource,
batch: ColumnBatch,
judged: bool,
) -> crate::Result<ColumnBatch> {
let declared: Vec<ProjectedField> = scan
.fields
.iter()
.filter(|f| projection::is_declared(f))
.cloned()
.collect();
if declared.is_empty() {
return Ok(without_values(batch));
}
let judged = scan
.predicate
.as_ref()
.filter(|p| !judged && p.apply == PredicateApply::Filter)
.and_then(|p| declared.iter().position(|f| f.column_id() == p.column_id));
read_fields(scan, cells, batch, &declared, judged)
}
pub(super) fn judge(
scan: &ColumnarScan,
cells: &CellSource,
batch: ColumnBatch,
) -> crate::Result<Vec<bool>> {
let rows = batch.row_count as usize;
let Some((predicate, field)) = scan
.predicate
.as_ref()
.filter(|p| p.apply == PredicateApply::Filter)
.and_then(|p| {
let field = scan
.fields
.iter()
.find(|f| projection::is_declared(f) && f.column_id() == p.column_id)?;
Some((p, field.clone()))
})
else {
return Ok(alloc::vec![true; rows]);
};
let read = read_fields(scan, cells, batch, core::slice::from_ref(&field), None)?;
let column = read
.columns
.into_iter()
.find(|c| c.column_id == field.column_id())
.ok_or(MISSING_BATCH_COLUMN)?;
let probe = ColumnBatch {
row_count: read.row_count,
columns: alloc::vec![column],
};
let (probe, _) = projection::conform_lenient(
probe,
core::slice::from_ref(&field),
Some(field.column_id()),
)?;
Ok(predicate.matching_rows(&probe))
}
fn read_fields(
scan: &ColumnarScan,
cells: &CellSource,
batch: ColumnBatch,
declared: &[ProjectedField],
judged: Option<usize>,
) -> crate::Result<ColumnBatch> {
let row_count = batch.row_count as usize;
let find = |id: u16| batch.columns.iter().find(|c| c.column_id == id);
let (Some(keys), Some(types)) = (find(COL_USER_KEY), find(COL_VALUE_TYPE)) else {
return Err(MISSING_BATCH_COLUMN);
};
let whole_col = find(COL_WHOLE_VALUE);
let refs_col = find(CELL_REFS_COLUMN);
let mut rows = Rows {
keys: Vec::with_capacity(row_count),
cells: Vec::with_capacity(declared.len()),
whole: Vec::new(),
};
for field in declared {
let column = find(field.column_id());
let mut cells = Vec::with_capacity(row_count);
for row in 0..batch.row_count {
cells.push(match column {
Some(column) if column.is_valid(row) => {
Some(Slice::from(cell_of(column, batch.row_count, row)?))
}
_ => None,
});
}
rows.cells.push(cells);
}
let mut now: Vec<Fetch> = Vec::new();
let mut later: Vec<Fetch> = Vec::new();
let mut out_types: Vec<u8> = Vec::with_capacity(row_count);
let mut mistyped: Vec<u32> = Vec::new();
for row in 0..batch.row_count {
let at = row as usize;
rows.keys.push(Slice::from(bytes_column_row(
&keys.data,
batch.row_count,
row,
)?));
let byte = *types.data.get(at).ok_or(MISSING_BATCH_COLUMN)?;
let value_type =
ValueType::try_from(byte).map_err(|()| Error::InvalidTag(("ValueType", byte)))?;
let whole = match whole_col {
Some(column) if column.is_valid(row) => {
Some(bytes_column_row(&column.data, batch.row_count, row)?)
}
_ => None,
};
let mut reference = |field: usize, indirection: BlobIndirection| {
let fetch = Fetch {
row: at,
field: Some(field),
indirection,
};
if judged == Some(field) {
now.push(fetch);
} else {
later.push(fetch);
}
};
match (value_type, whole) {
(ValueType::CellRow, Some(stored)) => {
let fields = decode_row(stored)?;
for (index, field) in declared.iter().enumerate() {
let held = fields.iter().find(|f| f.column == field.column_id());
let slot = rows
.cells
.get_mut(index)
.and_then(|cells| cells.get_mut(at))
.ok_or(MISSING_BATCH_COLUMN)?;
*slot = None;
let Some(held) = held else {
continue;
};
if !holds_declared_type(field, held, judged == Some(index))? {
mistyped.push(row);
continue;
}
match held.cell {
RowCell::Value(bytes) => *slot = Some(Slice::from(bytes)),
RowCell::Ref { indirection, .. } => reference(index, indirection),
}
}
out_types.push(u8::from(ValueType::Value));
}
(ValueType::CellRow, None) => {
if let Some(column) = refs_col.filter(|c| c.is_valid(row)) {
let listed = bytes_column_row(&column.data, batch.row_count, row)?;
for held in cell_refs(listed)? {
let Some(index) =
declared.iter().position(|f| f.column_id() == held.column)
else {
continue;
};
if let Some(field) = declared.get(index)
&& !holds_declared_type(field, &held, judged == Some(index))?
{
mistyped.push(row);
continue;
}
if let RowCell::Ref { indirection, .. } = held.cell {
reference(index, indirection);
}
}
}
out_types.push(u8::from(ValueType::Value));
}
(ValueType::Indirection, Some(stored)) => {
let mut reader = stored;
let indirection = BlobIndirection::decode_from(&mut reader)?;
let fetch = Fetch {
row: at,
field: None,
indirection,
};
if judged.is_some() {
now.push(fetch);
} else {
later.push(fetch);
}
out_types.push(u8::from(ValueType::Value));
}
(_, Some(value)) => {
rows.whole.push((at, Slice::from(value)));
out_types.push(byte);
}
(_, None) => out_types.push(byte),
}
}
read_objects(cells, &mut rows, now)?;
project(scan, declared, &mut rows)?;
let mut kept: Option<Vec<u32>> = None;
if let (Some(index), Some(predicate)) = (judged, scan.predicate.as_ref())
&& let Some(field) = declared.get(index)
{
let probe = ColumnBatch {
row_count: batch.row_count,
columns: alloc::vec![built_column(
field,
rows.cells.get(index).ok_or(MISSING_BATCH_COLUMN)?
)?],
};
let (probe, _) = projection::conform_lenient(
probe,
core::slice::from_ref(field),
Some(field.column_id()),
)?;
let matching = predicate.matching_rows(&probe);
if matching.iter().any(|keep| !keep) {
let indices: Vec<u32> = (0..batch.row_count)
.zip(&matching)
.filter(|&(_, &keep)| keep)
.map(|(row, _)| row)
.collect();
later.retain(|fetch| matching.get(fetch.row).copied().unwrap_or(false));
kept = Some(indices);
}
}
let returned = |row: &u32| {
kept.as_ref()
.is_none_or(|kept| kept.binary_search(row).is_ok())
};
if mistyped.iter().any(returned) {
return Err(projection::MISTYPED);
}
read_objects(cells, &mut rows, later)?;
project(scan, declared, &mut rows)?;
build(batch, declared, &rows, &out_types, kept.as_deref())
}
fn without_values(batch: ColumnBatch) -> ColumnBatch {
let ColumnBatch {
row_count,
mut columns,
} = batch;
columns.retain(|c| c.column_id != COL_WHOLE_VALUE && c.column_id != CELL_REFS_COLUMN);
if let Some(types) = columns.iter_mut().find(|c| c.column_id == COL_VALUE_TYPE) {
let read_as_value = |byte: u8| {
if byte == u8::from(ValueType::CellRow) || byte == u8::from(ValueType::Indirection) {
u8::from(ValueType::Value)
} else {
byte
}
};
if types.data.iter().any(|&byte| read_as_value(byte) != byte) {
types.data = Slice::from(
types
.data
.iter()
.map(|&b| read_as_value(b))
.collect::<Vec<u8>>(),
);
}
}
ColumnBatch { row_count, columns }
}
fn holds_declared_type(
field: &ProjectedField,
held: &RowField<'_>,
judged: bool,
) -> crate::Result<bool> {
if field.type_tag() == Some(held.tag) {
return Ok(true);
}
if judged {
return Err(projection::MISTYPED);
}
Ok(false)
}
fn read_objects(cells: &CellSource, rows: &mut Rows, mut fetches: Vec<Fetch>) -> crate::Result<()> {
fetches.sort_by_key(|fetch| {
(
fetch.indirection.vhandle.blob_file_id,
fetch.indirection.vhandle.offset,
)
});
let mut prefetched = crate::vlog::Prefetched::default();
if fetches.len() > 1 {
let mut items = Vec::with_capacity(fetches.len());
for fetch in &fetches {
let key = rows.keys.get(fetch.row).ok_or(MISSING_BATCH_COLUMN)?;
items.push((&**key, fetch.indirection.vhandle, 0));
}
cells
.source
.prefetch(&cells.version.version, &mut items, &mut prefetched);
}
for fetch in fetches {
let key = rows.keys.get(fetch.row).ok_or(MISSING_BATCH_COLUMN)?;
let object = cells.source.object_prefetched(
&cells.version.version,
key,
&fetch.indirection,
&mut prefetched,
)?;
if object.len() != fetch.indirection.size as usize {
return Err(Error::InvalidHeader(
"columnar_scan: a referenced object differs from its recorded size",
));
}
match fetch.field {
Some(index) => {
let slot = rows
.cells
.get_mut(index)
.and_then(|cells| cells.get_mut(fetch.row))
.ok_or(MISSING_BATCH_COLUMN)?;
*slot = Some(object);
}
None => rows.whole.push((fetch.row, object)),
}
}
Ok(())
}
fn project(scan: &ColumnarScan, declared: &[ProjectedField], rows: &mut Rows) -> crate::Result<()> {
if rows.whole.is_empty() {
return Ok(());
}
let projector = scan.projector.as_deref().ok_or(projection::UNREADABLE)?;
let mut out: Vec<Option<Vec<u8>>> = alloc::vec![None; declared.len()];
for (row, value) in core::mem::take(&mut rows.whole) {
out.fill(None);
let key = rows.keys.get(row).ok_or(MISSING_BATCH_COLUMN)?;
projector.project(key, &value, &mut ProjectedRow::new(declared, &mut out))?;
for (index, cell) in out.iter_mut().enumerate() {
let slot = rows
.cells
.get_mut(index)
.and_then(|cells| cells.get_mut(row))
.ok_or(MISSING_BATCH_COLUMN)?;
*slot = cell.take().map(Slice::from);
}
}
Ok(())
}
fn cell_of(column: &Column, rows: u32, row: u32) -> crate::Result<&[u8]> {
match column.type_tag.fixed_width() {
Some(width) => {
let width = usize::from(width);
let start = row as usize * width;
column
.data
.get(start..start + width)
.ok_or(MISSING_BATCH_COLUMN)
}
None => bytes_column_row(&column.data, rows, row),
}
}
fn built_column(field: &ProjectedField, cells: &[Option<Slice>]) -> crate::Result<Column> {
let type_tag = field.type_tag().ok_or(projection::MISTYPED)?;
let count = cells.len();
let validity = cells.iter().any(Option::is_none).then(|| {
let mut bits = alloc::vec![0u8; count.div_ceil(8)];
for (row, cell) in cells.iter().enumerate() {
if cell.is_some()
&& let Some(byte) = bits.get_mut(row / 8)
{
*byte |= 1 << (row % 8);
}
}
bits
});
let data = match type_tag.fixed_width() {
Some(width) => {
let width = usize::from(width);
let mut data = alloc::vec![0u8; count * width];
for (slot, cell) in data.chunks_exact_mut(width).zip(cells) {
if let Some(cell) = cell {
if cell.len() != width {
return Err(projection::MISTYPED);
}
slot.copy_from_slice(cell);
}
}
Slice::from(data)
}
None => frame_bytes_column(count, || cells.iter().map(|c| c.as_deref().unwrap_or(&[])))?,
};
Ok(Column {
column_id: field.column_id(),
type_tag,
validity,
data,
})
}
fn build(
batch: ColumnBatch,
declared: &[ProjectedField],
rows: &Rows,
types: &[u8],
kept: Option<&[u32]>,
) -> crate::Result<ColumnBatch> {
let row_count = batch.row_count as usize;
let pick = |cells: &[Option<Slice>]| -> Vec<Option<Slice>> {
match kept {
Some(kept) => kept
.iter()
.map(|&row| cells.get(row as usize).cloned().flatten())
.collect(),
None => cells.to_vec(),
}
};
let mut columns = Vec::with_capacity(batch.columns.len());
for column in batch.columns {
match column.column_id {
COL_WHOLE_VALUE | CELL_REFS_COLUMN => {}
COL_VALUE_TYPE => {
let data: Vec<u8> = match kept {
Some(kept) => kept
.iter()
.map(|&row| types.get(row as usize).copied().ok_or(MISSING_BATCH_COLUMN))
.collect::<crate::Result<_>>()?,
None => types.to_vec(),
};
columns.push(Column {
data: Slice::from(data),
validity: None,
..column
});
}
id => match declared.iter().position(|f| f.column_id() == id) {
Some(index) => {
let field = declared.get(index).ok_or(MISSING_BATCH_COLUMN)?;
let cells = rows.cells.get(index).ok_or(MISSING_BATCH_COLUMN)?;
columns.push(built_column(field, &pick(cells))?);
}
None => columns.push(match kept {
Some(kept) => take_column(&column, row_count, kept)?,
None => column,
}),
},
}
}
for (index, field) in declared.iter().enumerate() {
if !columns.iter().any(|c| c.column_id == field.column_id()) {
let cells = rows.cells.get(index).ok_or(MISSING_BATCH_COLUMN)?;
columns.push(built_column(field, &pick(cells))?);
}
}
let row_count = kept.map_or(row_count, <[u32]>::len);
Ok(ColumnBatch {
row_count: u32::try_from(row_count).unwrap_or(batch.row_count),
columns,
})
}