use super::error::ProtocolError;
use super::types::{check_not_null, Schema, ZSetBatch};
use gnitz_wire::{as_le_bytes, Regions, REG_NULL_BMP, REG_PAYLOAD_START, REG_PK, REG_WEIGHT};
impl ZSetBatch {
pub(crate) fn wire_regions(&self) -> Regions<'_> {
let mut out = Regions::new();
out.push(self.pks.region());
out.push(as_le_bytes(&self.weights));
out.push(as_le_bytes(&self.nulls));
for c in &self.payload {
out.push(&c.bytes);
}
out.push(&self.blob);
out
}
}
pub(crate) fn decode_wal_block_into(sink: &mut ZSetBatch, data: &[u8], schema: &Schema) -> Result<(), ProtocolError> {
let layout = schema.layout();
let (rows, fixed, heap, _) = gnitz_wire::wal::parse_block(data, layout.row_width())
.map_err(|e| ProtocolError::DecodeError(format!("WAL {e}")))?;
let mut regions = Regions::new();
for r in 0..layout.num_regions() {
regions.push(&fixed[layout.region_start(r, rows)..][..rows * layout.region_stride(r)]);
}
regions.push(heap);
append_regions(sink, ®ions, schema, true)
}
pub fn append_own_regions(sink: &mut ZSetBatch, regions: &[&[u8]], schema: &Schema) -> Result<(), ProtocolError> {
append_regions(sink, regions, schema, false)
}
fn append_regions(
sink: &mut ZSetBatch,
regions: &[&[u8]],
schema: &Schema,
foreign: bool,
) -> Result<(), ProtocolError> {
debug_assert!(sink.layout_matches(schema).is_ok() && sink.check_columns().is_ok());
let count = regions.get(REG_WEIGHT).map_or(0, |w| w.len() / 8);
if regions.len() != gnitz_wire::num_regions(sink.payload.len())
|| regions[REG_PK].len() != count * sink.pks.stride()
|| regions[REG_NULL_BMP].len() != count * 8
|| !sink
.payload
.iter()
.zip(®ions[REG_PAYLOAD_START..])
.all(|(c, r)| r.len() == count * c.stride())
{
return Err(ProtocolError::DecodeError(
"a region list that does not lay out its schema".into(),
));
}
sink.reserve(count);
sink.pks.push_region_bytes(regions[REG_PK]);
gnitz_wire::extend_from_le_bytes(&mut sink.weights, regions[REG_WEIGHT]);
let nulls_at = sink.nulls.len();
gnitz_wire::extend_from_le_bytes(&mut sink.nulls, regions[REG_NULL_BMP]);
check_not_null(&sink.nulls[nulls_at..], schema).map_err(ProtocolError::DecodeError)?;
let block_blob = regions[REG_PAYLOAD_START + sink.payload.len()];
let blob_base = sink.blob.len();
sink.blob.extend_from_slice(block_blob);
for (pi, col) in sink.payload.iter_mut().enumerate() {
let german = col.tc().is_german_string();
let dst = &mut col.bytes;
let at = dst.len();
dst.extend_from_slice(regions[REG_PAYLOAD_START + pi]);
if german {
if foreign && !gnitz_wire::german_string_region_ok(&dst[at..], block_blob) {
return Err(ProtocolError::DecodeError(format!(
"column {}: German string cell is not in canonical form",
schema.layout().payload_col_idx(pi)
)));
}
gnitz_wire::shift_german_string_heaps(&mut dst[at..], blob_base);
}
}
Ok(())
}
#[cfg(test)]
#[path = "tests/wal_block.rs"]
mod tests;
#[cfg(test)]
#[path = "benches/wal_block.rs"]
mod benches;