use super::{u32_len, write_value_streaming, CanonicalSegmentError, CheckpointWorkContext};
use crate::background::CheckpointWorkError;
use hawdb_core::{LabelId, Value};
use hawdb_integrity::Crc32cHasher;
use std::io::{self, Write};
pub(super) fn key(
label: LabelId,
property: &str,
value: &Value,
work: Option<&CheckpointWorkContext>,
) -> Result<u64, CanonicalSegmentError> {
let mut output = Hash {
crc: Crc32cHasher::new(),
work,
failure: None,
};
let result = (|| {
output.write_all(&label.0.to_le_bytes())?;
output.write_all(&u32_len(property.len(), "string")?.to_le_bytes())?;
output.write_all(property.as_bytes())?;
write_value_streaming(&mut output, value, 0)
})();
if let Some(error) = output.failure {
return Err(CanonicalSegmentError::Work(error));
}
result?;
if let Some(work) = work {
work.checkpoint()?;
}
Ok(output.crc.finish())
}
struct Hash<'a> {
crc: Crc32cHasher,
work: Option<&'a CheckpointWorkContext>,
failure: Option<CheckpointWorkError>,
}
impl Hash<'_> {
fn stopped(&mut self, error: CheckpointWorkError) -> io::Error {
self.failure = Some(error);
io::ErrorKind::Other.into()
}
}
impl Write for Hash<'_> {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
for block in bytes.chunks(64 * 1024) {
let unit = match self.work.map(CheckpointWorkContext::start_unit).transpose() {
Ok(unit) => unit,
Err(error) => return Err(self.stopped(error)),
};
self.crc.update(block);
if let Some(unit) = unit {
unit.finish();
}
if let Some(work) = self.work
&& let Err(error) = work.checkpoint()
{
return Err(self.stopped(error));
}
}
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests;