use std::io::Write;
use crate::error::{Error, Result};
use crate::external_sort::{SortedPasses, SpillRecord};
use crate::index::lucene::codec::data_output::CodecOutput;
const DATA_CODEC: &str = "Lucene41NormsData";
const META_CODEC: &str = "Lucene41NormsMetadata";
const VERSION_CURRENT: i32 = 1;
const NUMBER: u8 = 0;
const UNCOMPRESSED: u8 = 2;
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct NormRecord {
pub document: i32,
pub norm: u8,
}
impl SpillRecord for NormRecord {
fn encode(&self, buffer: &mut Vec<u8>) {
buffer.extend_from_slice(&self.document.to_be_bytes());
buffer.push(self.norm);
}
fn decode(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 5 {
return Err(Error::InvalidFormat {
details: format!("a norm record is five bytes, not {}", bytes.len()),
});
}
Ok(Self {
document: i32::from_be_bytes(bytes[..4].try_into().expect("four bytes")),
norm: bytes[4],
})
}
fn resident_size(&self) -> usize {
5
}
}
#[derive(Debug)]
pub struct NormsFiles<Sink> {
pub data: Sink,
pub metadata: Sink,
}
#[must_use]
pub fn float_to_byte_315(value: f32) -> u8 {
const ZERO_POINT: i32 = (63 - 15) << 3;
let bits = value.to_bits() as i32;
let small = bits >> (24 - 3);
if small <= ZERO_POINT {
return u8::from(bits > 0);
}
if small >= ZERO_POINT + 0x100 {
return 0xff;
}
(small - ZERO_POINT) as u8
}
#[must_use]
pub fn norm_byte(boost: f32, term_count: u32) -> u8 {
#[expect(
clippy::cast_possible_truncation,
reason = "the single narrowing to float is the rule, not an approximation of it"
)]
let reciprocal = (1.0 / f64::from(term_count).sqrt()) as f32;
let value = boost * reciprocal;
if value.is_nan() {
return 0;
}
float_to_byte_315(value)
}
pub struct NormsConsumer<Sink: Write> {
data: CodecOutput<Sink>,
metadata: CodecOutput<Sink>,
document_count: i64,
}
impl<Sink: Write> NormsConsumer<Sink> {
pub fn new(data: Sink, metadata: Sink, document_count: i64) -> Result<Self> {
let mut data = CodecOutput::new(data);
let mut metadata = CodecOutput::new(metadata);
data.write_header(DATA_CODEC, VERSION_CURRENT)?;
metadata.write_header(META_CODEC, VERSION_CURRENT)?;
Ok(Self {
data,
metadata,
document_count,
})
}
pub fn add_field(
&mut self,
field_number: i32,
norms: &mut SortedPasses<NormRecord>,
) -> Result<()> {
self.metadata.write_vint(field_number)?;
self.metadata.write_byte(NUMBER)?;
self.metadata.write_long(self.data.position() as i64)?;
self.metadata.write_byte(UNCOMPRESSED)?;
let mut expected = 0i32;
for record in norms.pass()? {
let record = record?;
if record.document != expected {
return Err(Error::InvalidFormat {
details: format!(
"a norms stream is dense and in document order: document {expected} \
was expected and {} came",
record.document
),
});
}
self.data.write_byte(record.norm)?;
expected += 1;
}
if i64::from(expected) != self.document_count {
return Err(Error::InvalidFormat {
details: format!(
"a norms stream holds one byte per document: {expected} for a segment of \
{} documents",
self.document_count
),
});
}
Ok(())
}
pub fn finish(mut self) -> Result<NormsFiles<Sink>> {
self.metadata.write_vint(-1)?;
Ok(NormsFiles {
data: self.data.into_inner(),
metadata: self.metadata.into_inner(),
})
}
}