mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-10-29 14:57:10 +00:00
`Streamer::term_ord()` derived the ordinal by counting `delta_reader.advance()` calls from a seed taken only when the stream had a key lower bound. An automaton search has no key bounds, so the seed was 0 — but the reader it is handed *is* block-pruned by that automaton (`get_block_iterator_for_range_and_automaton`). Every block skipped ahead of the first match went uncounted, so `term_ord()` returned the term's position among the blocks actually scanned rather than its ordinal in the dictionary. The error is silent and grows with how deep the first match sits: on a 262k-term dictionary, a regex matching only the last key reported ordinal 999 instead of 262142. Callers that resolve those ordinals back to terms therefore act on a different term entirely — `build_allowed_term_ids_for_str` builds the terms aggregation's allowed-ordinal bitset this way, so an `include` regex could make the aggregation count unrelated terms while returning the expected bucket count. Broad patterns matching from the start of the dictionary hid it, since nothing is pruned ahead of the first match. Each slice handed to the reader now carries the ordinal of its first term, and the streamer resets to it on entering a slice instead of incrementing across the gap. Blocks merged into one slice stay contiguous, so counting within a slice is still correct. `Dictionary::sorted_ords_to_term_cb` already tracked `BlockAddr::first_ordinal` explicitly, which is why ordinal->term resolution was unaffected.
170 lines
5.5 KiB
Rust
170 lines
5.5 KiB
Rust
use std::io::{self, Read};
|
|
use std::ops::Range;
|
|
|
|
use common::OwnedBytes;
|
|
#[cfg(feature = "zstd-compression")]
|
|
use zstd::bulk::Decompressor;
|
|
|
|
pub struct BlockReader {
|
|
buffer: Vec<u8>,
|
|
reader: OwnedBytes,
|
|
next_readers: std::vec::IntoIter<(OwnedBytes, u64)>,
|
|
offset: usize,
|
|
/// First term ordinal of the slice we just moved to, taken once by the caller.
|
|
///
|
|
/// When an automaton prunes blocks, the slices handed to us are not contiguous, so a
|
|
/// caller counting terms cannot know the ordinal it is at. Each slice therefore carries
|
|
/// the ordinal of its first term; consecutive blocks merged into one slice stay
|
|
/// contiguous, so counting within a slice remains correct.
|
|
pending_first_ordinal: Option<u64>,
|
|
}
|
|
|
|
impl BlockReader {
|
|
pub fn new(reader: OwnedBytes) -> BlockReader {
|
|
BlockReader {
|
|
buffer: Vec::new(),
|
|
reader,
|
|
next_readers: Vec::new().into_iter(),
|
|
offset: 0,
|
|
pending_first_ordinal: None,
|
|
}
|
|
}
|
|
|
|
/// Build a reader over non-contiguous slices, each labelled with the term ordinal of its
|
|
/// first term. See [`BlockReader::take_first_ordinal`].
|
|
pub fn from_multiple_blocks(readers: Vec<(OwnedBytes, u64)>) -> BlockReader {
|
|
let mut next_readers = readers.into_iter();
|
|
let (reader, first_ordinal) = next_readers
|
|
.next()
|
|
.unwrap_or_else(|| (OwnedBytes::empty(), 0));
|
|
BlockReader {
|
|
buffer: Vec::new(),
|
|
reader,
|
|
next_readers,
|
|
offset: 0,
|
|
pending_first_ordinal: Some(first_ordinal),
|
|
}
|
|
}
|
|
|
|
/// The first term ordinal of the slice most recently moved to, if it has not been taken yet.
|
|
///
|
|
/// Returns `Some` exactly once per slice, on the first term read from it, so a caller can
|
|
/// reset its ordinal counter instead of incrementing across the gap left by pruned blocks.
|
|
pub fn take_first_ordinal(&mut self) -> Option<u64> {
|
|
self.pending_first_ordinal.take()
|
|
}
|
|
|
|
pub fn deserialize_u64(&mut self) -> u64 {
|
|
let (num_bytes, val) = super::vint::deserialize_read(self.buffer());
|
|
self.advance(num_bytes);
|
|
val
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn buffer_from_to(&self, range: Range<usize>) -> &[u8] {
|
|
&self.buffer[range]
|
|
}
|
|
|
|
pub fn read_block(&mut self) -> io::Result<bool> {
|
|
self.offset = 0;
|
|
self.buffer.clear();
|
|
|
|
loop {
|
|
let block_len = match self.reader.len() {
|
|
0 => {
|
|
// we are out of data for this block. Check if we have another block after
|
|
match self.next_readers.next() {
|
|
Some((new_reader, first_ordinal)) => {
|
|
self.reader = new_reader;
|
|
self.pending_first_ordinal = Some(first_ordinal);
|
|
continue;
|
|
}
|
|
_ => {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
}
|
|
1..=3 => {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::UnexpectedEof,
|
|
"failed to read block_len",
|
|
));
|
|
}
|
|
_ => self.reader.read_u32() as usize,
|
|
};
|
|
if block_len <= 1 {
|
|
return Ok(false);
|
|
}
|
|
let compress = self.reader.read_u8();
|
|
let block_len = block_len - 1;
|
|
|
|
if self.reader.len() < block_len {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::UnexpectedEof,
|
|
"failed to read block content",
|
|
));
|
|
}
|
|
if compress == 1 {
|
|
#[cfg(feature = "zstd-compression")]
|
|
{
|
|
let required_capacity =
|
|
Decompressor::upper_bound(&self.reader[..block_len]).unwrap_or(1024 * 1024);
|
|
self.buffer.reserve(required_capacity);
|
|
Decompressor::new()?
|
|
.decompress_to_buffer(&self.reader[..block_len], &mut self.buffer)?;
|
|
|
|
self.reader.advance(block_len);
|
|
}
|
|
|
|
if cfg!(not(feature = "zstd-compression")) {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::Unsupported,
|
|
"zstd-compression feature is not enabled",
|
|
));
|
|
}
|
|
} else {
|
|
self.buffer.resize(block_len, 0u8);
|
|
self.reader.read_exact(&mut self.buffer[..])?;
|
|
}
|
|
|
|
return Ok(true);
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn offset(&self) -> usize {
|
|
self.offset
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn advance(&mut self, num_bytes: usize) {
|
|
self.offset += num_bytes;
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn buffer(&self) -> &[u8] {
|
|
&self.buffer[self.offset..]
|
|
}
|
|
}
|
|
|
|
impl io::Read for BlockReader {
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
let len = self.buffer().read(buf)?;
|
|
self.advance(len);
|
|
Ok(len)
|
|
}
|
|
|
|
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
|
|
let len = self.buffer.len();
|
|
buf.extend_from_slice(self.buffer());
|
|
self.advance(len);
|
|
Ok(len)
|
|
}
|
|
|
|
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
|
|
self.buffer().read_exact(buf)?;
|
|
self.advance(buf.len());
|
|
Ok(())
|
|
}
|
|
}
|