use super::{IndexData, IndexHeader, PartitionIndexEntry, PromotedIndexData};
use crate::parser::vint::parse_vuint;
use crate::storage::scan_cancel::ScanCancel;
use crate::storage::sstable::header_spec::get_global_registry;
use crate::storage::sstable::summary_reader::SummaryReader;
use crate::Result;
use nom::{bytes::complete::take, number::complete::be_u16, IResult};
use std::collections::HashMap;
use std::sync::Arc;
const CANCEL_POLL_INTERVAL: usize = 1 << 16;
pub(super) fn parse_index_data_with_summary<'a>(
input: &'a [u8],
summary_reader: Option<&SummaryReader>,
) -> IResult<&'a [u8], IndexData> {
use nom::error::{Error as NomError, ErrorKind};
match parse_index_data_cancellable(input, summary_reader, &ScanCancel::default()) {
Ok(pair) => Ok(pair),
Err(crate::Error::Corruption(_)) => {
Err(nom::Err::Error(NomError::new(input, ErrorKind::Eof)))
}
Err(_) => Err(nom::Err::Error(NomError::new(input, ErrorKind::Fail))),
}
}
pub(super) fn parse_index_data_cancellable<'a>(
input: &'a [u8],
summary_reader: Option<&SummaryReader>,
cancel: &ScanCancel,
) -> Result<(&'a [u8], IndexData)> {
cancel.check()?;
let (remaining, header) = parse_index_header_prefix(input)?;
let (remaining, partition_entries) =
parse_all_partition_keys_cancellable(remaining, summary_reader, cancel)?;
let mut key_lookup = HashMap::with_capacity(partition_entries.len());
for (index, entry) in partition_entries.iter().enumerate() {
key_lookup.insert(Arc::clone(&entry.key_digest), index);
}
let header = IndexHeader {
entry_count: partition_entries.len() as u32,
..header
};
Ok((
remaining,
IndexData {
header,
partition_entries,
key_lookup,
},
))
}
fn parse_index_header_prefix(input: &[u8]) -> Result<(&[u8], IndexHeader)> {
let registry = get_global_registry();
match registry.parse_index_header(input) {
Ok(parsed_header) => {
tracing::debug!("Successfully parsed Index.db header using spec-driven approach");
let header = IndexHeader {
version: parsed_header
.fields
.get("version")
.and_then(|v| v.as_u32().ok())
.unwrap_or(1),
entry_count: parsed_header
.fields
.get("entry_count")
.and_then(|v| v.as_u32().ok())
.unwrap_or(0),
data_size: parsed_header
.fields
.get("data_size")
.and_then(|v| v.as_u64().ok())
.unwrap_or(input.len() as u64),
checksum: parsed_header
.fields
.get("checksum")
.and_then(|v| v.as_u32().ok())
.unwrap_or(0),
};
let header_size = parsed_header.header_size;
if input.len() < header_size {
return Err(crate::Error::corruption(
"Index.db header declares more bytes than the file holds",
));
}
Ok((&input[header_size..], header))
}
Err(_) => {
tracing::debug!("Spec-driven header parsing failed, assuming headerless format");
let header = IndexHeader {
version: 1,
entry_count: 0, data_size: input.len() as u64,
checksum: 0,
};
Ok((input, header))
}
}
}
pub(super) fn parse_all_partition_keys_with_summary<'a>(
input: &'a [u8],
summary_reader: Option<&SummaryReader>,
) -> IResult<&'a [u8], Vec<PartitionIndexEntry>> {
use nom::error::{Error as NomError, ErrorKind};
match parse_all_partition_keys_cancellable(input, summary_reader, &ScanCancel::default()) {
Ok(pair) => Ok(pair),
Err(crate::Error::Corruption(_)) => {
Err(nom::Err::Error(NomError::new(input, ErrorKind::Eof)))
}
Err(_) => Err(nom::Err::Error(NomError::new(input, ErrorKind::Fail))),
}
}
pub(super) fn parse_all_partition_keys_cancellable<'a>(
input: &'a [u8],
_summary_reader: Option<&SummaryReader>,
cancel: &ScanCancel,
) -> Result<(&'a [u8], Vec<PartitionIndexEntry>)> {
const AVG_ENTRY_BYTES: usize = 24;
const MAX_CAP_HINT: usize = 4_000_000;
let cap_hint = (input.len() / AVG_ENTRY_BYTES).min(MAX_CAP_HINT);
let mut entries = Vec::with_capacity(cap_hint);
let mut remaining = input;
let mut entry_index = 0usize;
while !remaining.is_empty() {
if entry_index % CANCEL_POLL_INTERVAL == 0 {
cancel.check()?;
}
match parse_big_index_entry(remaining) {
Ok((rest, entry)) => {
debug_assert!(
rest.len() < remaining.len(),
"BIG Index.db parser must make forward progress"
);
entries.push(entry);
remaining = rest;
entry_index += 1;
}
Err(_e) => {
tracing::debug!(
"Stopped parsing Index.db at entry {} with {} bytes remaining",
entry_index,
remaining.len()
);
break;
}
}
}
tracing::debug!("Parsed {} partition entries from Index.db", entries.len());
if remaining.is_empty() {
crate::observability::add_counter(
crate::observability::catalog::INDEX_PARSES_TOTAL,
1,
&[],
);
}
Ok((remaining, entries))
}
pub(crate) fn parse_big_index_entry(input: &[u8]) -> IResult<&[u8], PartitionIndexEntry> {
let (input, key_len) = be_u16(input)?;
let (input, key_bytes) = take(key_len)(input)?;
let (input, data_offset) = parse_vuint(input)?;
let (input, promoted_len) = parse_vuint(input)?;
let promoted_len = usize::try_from(promoted_len).unwrap_or(usize::MAX);
let (input, promoted_data) = take(promoted_len)(input)?;
tracing::trace!(
"Index.db BIG entry: key_len={}, data_offset={}, promoted_len={}",
key_len,
data_offset,
promoted_len
);
let promoted_index = if promoted_len > 0 {
Some(PromotedIndexData::from_raw(promoted_data.to_vec()))
} else {
None
};
let raw_key: Arc<[u8]> = Arc::from(key_bytes);
Ok((
input,
PartitionIndexEntry {
key_digest: Arc::clone(&raw_key),
raw_key: Some(raw_key),
data_offset,
data_size: 0,
promoted_index,
},
))
}
pub(crate) fn parse_big_index_entry_framing(input: &[u8]) -> IResult<&[u8], (usize, u64)> {
let original_len = input.len();
let (input, key_len) = be_u16(input)?;
let (input, _key_bytes) = take(key_len)(input)?;
let (input, _data_offset) = parse_vuint(input)?;
let (remaining, promoted_len) = parse_vuint(input)?;
let framing_len = original_len - remaining.len();
Ok((remaining, (framing_len, promoted_len)))
}
#[allow(dead_code)]
pub(super) fn parse_index_data(input: &[u8]) -> IResult<&[u8], IndexData> {
parse_index_data_with_summary(input, None)
}
#[allow(dead_code)]
pub(crate) fn parse_all_partition_keys(input: &[u8]) -> IResult<&[u8], Vec<PartitionIndexEntry>> {
parse_all_partition_keys_with_summary(input, None)
}
#[allow(dead_code)]
pub(super) fn parse_simple_partition_key(input: &[u8]) -> IResult<&[u8], PartitionIndexEntry> {
parse_big_index_entry(input)
}