use crate::{
error::Error,
storage::sstable::bti::node::{BtiNode, BtiNodeData, BtiNodeType, BtiResult},
};
use std::io::{Read, Seek, SeekFrom};
use super::node_decode::{classify_node_nibble, parse_bti_node, pointer_bytes_for_ordinal};
pub const FLAG_HAS_HASH_BYTE: u8 = 8;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BtiPartitionLocation {
DataOffset(u64),
RowsOffset(u64),
}
pub fn decode_bti_partition_payload(
trie_data: &[u8],
payload_start: usize,
payload_bits: u8,
) -> BtiResult<BtiPartitionLocation> {
if payload_bits < FLAG_HAS_HASH_BYTE {
return Err(Error::Parse(format!(
"BTI payload_bits {payload_bits} < FLAG_HAS_HASH_BYTE (8); \
hash-byte-less payloads are not supported in Cassandra 5.0 BTI format"
)));
}
if payload_bits > 16 {
return Err(Error::Parse(format!(
"BTI payload_bits {payload_bits} > 16; invalid BTI partition leaf"
)));
}
let position_bytes = (payload_bits - FLAG_HAS_HASH_BYTE + 1) as usize;
let needed = 1 + position_bytes;
if payload_start + needed > trie_data.len() {
return Err(Error::Parse(format!(
"BTI partition payload at {payload_start} is too short: \
need {needed} bytes, have {}",
trie_data.len().saturating_sub(payload_start)
)));
}
let pos_data = &trie_data[payload_start + 1..payload_start + 1 + position_bytes];
let position: i64 = sized_ints_read_from_slice(pos_data)?;
if position < 0 {
let data_offset = !position as u64; Ok(BtiPartitionLocation::DataOffset(data_offset))
} else {
Ok(BtiPartitionLocation::RowsOffset(position as u64))
}
}
pub(crate) fn sized_ints_read_from_slice(data: &[u8]) -> BtiResult<i64> {
match data.len() {
0 => Ok(0),
1 => Ok(data[0] as i8 as i64),
2 => Ok(i16::from_be_bytes([data[0], data[1]]) as i64),
3 => {
let high = data[0] as i8 as i64;
let low = u16::from_be_bytes([data[1], data[2]]) as i64;
Ok((high << 16) | low)
}
4 => Ok(i32::from_be_bytes([data[0], data[1], data[2], data[3]]) as i64),
5 => {
let high = data[0] as i8 as i64;
let low = u32::from_be_bytes([data[1], data[2], data[3], data[4]]) as i64;
Ok((high << 32) | low)
}
6 => {
let high = i16::from_be_bytes([data[0], data[1]]) as i64;
let low = u32::from_be_bytes([data[2], data[3], data[4], data[5]]) as i64;
Ok((high << 32) | low)
}
7 => {
let high1 = data[0] as i8 as i64;
let high2 = u16::from_be_bytes([data[1], data[2]]) as i64;
let low = u32::from_be_bytes([data[3], data[4], data[5], data[6]]) as i64;
Ok((high1 << 48) | (high2 << 32) | low)
}
8 => Ok(i64::from_be_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
])),
n => Err(Error::Parse(format!(
"SizedInts: invalid byte count {n} (expected 1–8)"
))),
}
}
pub(crate) fn parse_bti_node_for_traversal(
trie_data: &[u8],
node_offset: usize,
) -> BtiResult<BtiNode> {
if node_offset >= trie_data.len() {
return Err(Error::Parse(format!(
"BTI traversal: node_offset {node_offset} >= trie_data.len {}",
trie_data.len()
)));
}
let data = &trie_data[node_offset..];
let header_byte = data[0];
let ordinal = (header_byte >> 4) & 0x0F;
let payload_flags = header_byte & 0x0F;
let node_type = classify_node_nibble(ordinal)?;
match node_type {
BtiNodeType::PayloadOnly => {
if payload_flags == 0 {
return Err(Error::Parse(
"PayloadOnly node has no payload flags set".to_string(),
));
}
let stub = crate::storage::sstable::bti::node::PayloadRef::new(0, 0);
Ok(BtiNode {
node_type,
level: 0,
key_prefix: Vec::new(),
data: BtiNodeData::PayloadOnly { payload: stub },
})
}
BtiNodeType::Single => {
parse_bti_node(data, node_offset as u64)
}
BtiNodeType::Sparse => parse_bti_node(data, node_offset as u64),
BtiNodeType::Dense => parse_bti_node(data, node_offset as u64),
}
}
fn find_next_child_offset(
trie_data: &[u8],
node_offset: usize,
search_byte: u8,
) -> BtiResult<Option<usize>> {
super::slice_walk::find_child_offset(trie_data, node_offset, search_byte)
}
pub(crate) fn read_node_payload(
trie_data: &[u8],
node_offset: usize,
parsed: Option<&BtiNode>,
) -> BtiResult<Option<BtiPartitionLocation>> {
let node = parsed;
if node_offset >= trie_data.len() {
return Err(Error::Parse(format!(
"BTI payload read: node_offset {node_offset} out of bounds"
)));
}
let header_byte = trie_data[node_offset];
let ordinal = (header_byte >> 4) & 0x0F;
let payload_flags = header_byte & 0x0F;
if ordinal == 1 || ordinal == 3 {
return Ok(None);
}
if ordinal == 0 {
if payload_flags == 0 {
return Err(Error::Parse(
"PayloadOnly node has zero payload_flags".to_string(),
));
}
let payload_start = node_offset + 1; Ok(Some(decode_bti_partition_payload(
trie_data,
payload_start,
payload_flags,
)?))
} else if payload_flags != 0 {
let owned;
let node = match node {
Some(n) => n,
None => {
owned = parse_bti_node(&trie_data[node_offset..], node_offset as u64)?;
&owned
}
};
let payload_start = payload_start_in_node(node, trie_data, node_offset)?;
Ok(Some(decode_bti_partition_payload(
trie_data,
payload_start,
payload_flags,
)?))
} else {
Ok(None)
}
}
pub(crate) fn payload_start_in_node(
node: &BtiNode,
trie_data: &[u8],
node_offset: usize,
) -> BtiResult<usize> {
use BtiNodeData::*;
let header_byte = trie_data[node_offset];
let ordinal = (header_byte >> 4) & 0x0F;
let payload_offset = match &node.data {
PayloadOnly { .. } => {
node_offset + 1
}
Single { .. } => {
let ptr_bytes = pointer_bytes_for_ordinal(ordinal) as usize;
node_offset + 1 + 1 + ptr_bytes
}
Sparse { transitions } => {
let count = transitions.len();
let ptr_bytes = pointer_bytes_for_ordinal(ordinal) as usize;
let ptr_area = if ordinal == 6 {
(count * 3).div_ceil(2)
} else {
count * ptr_bytes
};
node_offset + 1 + 1 + count + ptr_area }
Dense { children, .. } => {
let range_len = children.len();
let ptr_bytes = pointer_bytes_for_ordinal(ordinal) as usize;
let ptr_area = if ordinal == 10 {
(range_len * 3).div_ceil(2)
} else {
range_len * ptr_bytes
};
node_offset + 1 + 1 + 1 + ptr_area }
};
Ok(payload_offset)
}
pub(crate) fn walk_bti_trie(
trie_data: &[u8],
root_offset: usize,
encoded_key: &[u8],
) -> BtiResult<Option<BtiPartitionLocation>> {
let mut current_offset = root_offset;
let mut key_pos = 0;
loop {
if current_offset >= trie_data.len() {
return Err(Error::Parse(format!(
"BTI trie walk: offset {current_offset} out of bounds (trie size {})",
trie_data.len()
)));
}
let header_byte = trie_data[current_offset];
let ordinal = (header_byte >> 4) & 0x0F;
let payload_flags = header_byte & 0x0F;
let is_leaf = ordinal == 0;
if is_leaf {
return read_node_payload(trie_data, current_offset, None);
}
if key_pos >= encoded_key.len() {
if payload_flags != 0 {
return read_node_payload(trie_data, current_offset, None);
}
return Ok(None);
}
let next_byte = encoded_key[key_pos];
match find_next_child_offset(trie_data, current_offset, next_byte)? {
Some(child_offset) => {
current_offset = child_offset;
key_pos += 1;
}
None => {
return Ok(None);
}
}
}
}
pub fn lookup_partition_in_bti_file<R: Read + Seek>(
reader: &mut R,
encoded_key: &[u8],
) -> BtiResult<Option<BtiPartitionLocation>> {
let file_size = reader.seek(SeekFrom::End(0))?;
if file_size < 8 {
return Err(Error::Parse(format!(
"BTI Partitions.db is too small ({file_size} bytes; need at least 8 for footer)"
)));
}
reader.seek(SeekFrom::End(-8))?;
let mut footer_buf = [0u8; 8];
reader.read_exact(&mut footer_buf)?;
let root_offset = u64::from_be_bytes(footer_buf);
let trie_size = file_size - 8;
if root_offset >= trie_size {
return Err(Error::Parse(format!(
"BTI Partitions.db: root_offset {root_offset} >= trie_size {trie_size}"
)));
}
reader.seek(SeekFrom::Start(0))?;
let mut trie_data = vec![0u8; trie_size as usize];
reader.read_exact(&mut trie_data)?;
walk_bti_trie(&trie_data, root_offset as usize, encoded_key)
}
pub fn encode_partition_key_for_bti_trie(raw_key_bytes: &[u8]) -> [u8; 9] {
crate::storage::sstable::read_work_counters::record_key_hash();
encode_partition_key_for_bti_trie_uncounted(raw_key_bytes)
}
pub(crate) fn encode_partition_key_for_bti_trie_uncounted(raw_key_bytes: &[u8]) -> [u8; 9] {
use crate::util::cassandra_murmur3::cassandra_murmur3_token;
let token: i64 = cassandra_murmur3_token(raw_key_bytes);
encode_bti_trie_key_from_token(token)
}
pub fn encode_bti_trie_key_from_token(token: i64) -> [u8; 9] {
let bc: u64 = (token as u64) ^ 0x8000_0000_0000_0000u64;
let bc_bytes = bc.to_be_bytes();
let mut key = [0u8; 9];
key[0] = 0x40; key[1..9].copy_from_slice(&bc_bytes);
key
}
pub fn lookup_raw_key_in_bti_partitions_db<R: Read + Seek>(
partitions_db_reader: &mut R,
raw_key_bytes: &[u8],
) -> BtiResult<Option<BtiPartitionLocation>> {
let encoded = encode_partition_key_for_bti_trie(raw_key_bytes);
lookup_partition_in_bti_file(partitions_db_reader, &encoded)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_bti_partition_payload_data_offset_zero() {
let header_byte: u8 = 0x08;
let hash_byte: u8 = 0x24;
let position_byte: u8 = 0xFF; let trie_data = vec![header_byte, hash_byte, position_byte, 0x00, 0x00];
let payload_start = 1; let payload_bits = header_byte & 0x0F; let result = decode_bti_partition_payload(&trie_data, payload_start, payload_bits)
.expect("should decode successfully");
assert_eq!(
result,
BtiPartitionLocation::DataOffset(0),
"position=-1 (0xFF as i8) must map to data_offset=0 via ~(-1)=0"
);
}
#[test]
fn decode_bti_partition_payload_data_offset_63() {
let trie_data = vec![
0x08u8, 0x22, 0xC0, ];
let payload_bits = 8u8;
let result = decode_bti_partition_payload(&trie_data, 1, payload_bits).unwrap();
assert_eq!(result, BtiPartitionLocation::DataOffset(63));
}
#[test]
fn decode_bti_partition_payload_data_offset_125() {
let trie_data = vec![
0x08u8, 0xF4, 0x82, ];
let result = decode_bti_partition_payload(&trie_data, 1, 8).unwrap();
assert_eq!(result, BtiPartitionLocation::DataOffset(125));
}
#[test]
fn decode_bti_partition_payload_rows_offset() {
let trie_data = vec![
0x09u8, 0xAB, 0x01, 0x00, ];
let result = decode_bti_partition_payload(&trie_data, 1, 9).unwrap();
assert_eq!(result, BtiPartitionLocation::RowsOffset(256));
}
#[test]
fn decode_bti_partition_payload_no_hash_byte_returns_error() {
let trie_data = vec![0x07u8, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
let err = decode_bti_partition_payload(&trie_data, 1, 7);
assert!(err.is_err(), "payloadBits < 8 must be an error");
}
#[test]
fn decode_bti_partition_payload_2byte_position() {
let trie_data = vec![0x09u8, 0xAB, 0x00, 0xC0];
let result = decode_bti_partition_payload(&trie_data, 1, 9).unwrap();
assert_eq!(result, BtiPartitionLocation::RowsOffset(192));
}
#[test]
fn sized_ints_slice_1_byte_positive() {
assert_eq!(sized_ints_read_from_slice(&[0x7F]).unwrap(), 127);
}
#[test]
fn sized_ints_slice_1_byte_negative() {
assert_eq!(sized_ints_read_from_slice(&[0xFF]).unwrap(), -1);
assert_eq!(sized_ints_read_from_slice(&[0xC0]).unwrap(), -64);
assert_eq!(sized_ints_read_from_slice(&[0x82]).unwrap(), -126);
}
#[test]
fn sized_ints_slice_2_bytes() {
assert_eq!(sized_ints_read_from_slice(&[0x00, 0xFF]).unwrap(), 255);
assert_eq!(sized_ints_read_from_slice(&[0xFF, 0x00]).unwrap(), -256);
}
#[test]
fn walk_bti_trie_payload_only_root_with_empty_key() {
let trie_data: Vec<u8> = vec![
0x08, 0x00, 0xFF, ];
let result = walk_bti_trie(&trie_data, 0, &[]).unwrap();
assert_eq!(result, Some(BtiPartitionLocation::DataOffset(0)));
}
#[test]
fn walk_bti_trie_two_partitions_via_sparse8_root() {
let mut trie_data = vec![0u8; 12];
trie_data[0] = 0x08; trie_data[1] = 0x11; trie_data[2] = 0xFF;
trie_data[3] = 0x08;
trie_data[4] = 0x22; trie_data[5] = 0xBF;
trie_data[6] = 0x50; trie_data[7] = 0x02; trie_data[8] = 0xAA; trie_data[9] = 0xBB; trie_data[10] = 0x06; trie_data[11] = 0x03;
let result_a = walk_bti_trie(&trie_data, 6, &[0xAA]).unwrap();
assert_eq!(
result_a,
Some(BtiPartitionLocation::DataOffset(0)),
"key 0xAA should resolve to data_offset=0"
);
let result_b = walk_bti_trie(&trie_data, 6, &[0xBB]).unwrap();
assert_eq!(
result_b,
Some(BtiPartitionLocation::DataOffset(64)),
"key 0xBB should resolve to data_offset=64"
);
let result_miss = walk_bti_trie(&trie_data, 6, &[0xCC]).unwrap();
assert_eq!(result_miss, None, "key 0xCC should not be found");
}
#[test]
fn lookup_partition_in_bti_file_synthetic_two_partitions() {
use std::io::Cursor;
let mut trie_file = vec![0u8; 12 + 8];
trie_file[0] = 0x08;
trie_file[1] = 0x11;
trie_file[2] = 0xFF;
trie_file[3] = 0x08;
trie_file[4] = 0x22;
trie_file[5] = 0xBF;
trie_file[6] = 0x50;
trie_file[7] = 0x02;
trie_file[8] = 0xAA;
trie_file[9] = 0xBB;
trie_file[10] = 0x06;
trie_file[11] = 0x03;
trie_file[12..20].copy_from_slice(&6u64.to_be_bytes());
{
let mut cursor = Cursor::new(trie_file.clone());
let result =
lookup_partition_in_bti_file(&mut cursor, &[0xAA]).expect("lookup must not error");
assert_eq!(
result,
Some(BtiPartitionLocation::DataOffset(0)),
"Trie lookup for key 0xAA must return DataOffset(0), NOT a sequential scan"
);
}
{
let mut cursor = Cursor::new(trie_file.clone());
let result =
lookup_partition_in_bti_file(&mut cursor, &[0xBB]).expect("lookup must not error");
assert_eq!(
result,
Some(BtiPartitionLocation::DataOffset(64)),
"Trie lookup for key 0xBB must return DataOffset(64)"
);
}
{
let mut cursor = Cursor::new(trie_file.clone());
let result =
lookup_partition_in_bti_file(&mut cursor, &[0xCC]).expect("lookup must not error");
assert_eq!(result, None, "Key 0xCC must not be found");
}
}
#[test]
fn lookup_partition_in_bti_file_real_simple_table_fixture() {
use std::fs::File;
let datasets_root = match std::env::var("CQLITE_DATASETS_ROOT") {
Ok(v) => std::path::PathBuf::from(v),
Err(_) => {
eprintln!(
"SKIP: CQLITE_DATASETS_ROOT not set; \
test requires real BTI fixture files"
);
return;
}
};
let partitions_db = datasets_root.join(
"sstables/test_da/simple_table-de1be8b064e711f19ad401a8c8227b11/da-2-bti-Partitions.db",
);
if !partitions_db.exists() {
eprintln!("SKIP: BTI fixture not found at {:?}", partitions_db);
return;
}
let mut file = File::open(&partitions_db)
.unwrap_or_else(|e| panic!("Cannot open {:?}: {}", partitions_db, e));
let file_size = {
use std::io::Seek;
file.seek(SeekFrom::End(0)).unwrap()
};
assert_eq!(file_size, 79, "simple_table Partitions.db must be 79 bytes");
{
use std::io::Seek;
file.seek(SeekFrom::End(-8)).unwrap();
}
let mut footer = [0u8; 8];
file.read_exact(&mut footer).unwrap();
let root_offset = u64::from_be_bytes(footer);
assert_eq!(
root_offset, 17,
"simple_table Partitions.db root must be at offset 17"
);
{
use std::io::Seek;
file.seek(SeekFrom::Start(0)).unwrap();
}
let mut trie_data = vec![0u8; 71]; file.read_exact(&mut trie_data).unwrap();
let loc0 =
decode_bti_partition_payload(&trie_data, 1, 8).expect("leaf at offset 0 must decode");
assert_eq!(
loc0,
BtiPartitionLocation::DataOffset(0),
"leaf at trie offset 0 must map to Data.db position 0 (UUID 22222222...)"
);
let loc63 =
decode_bti_partition_payload(&trie_data, 4, 8).expect("leaf at offset 3 must decode");
assert_eq!(
loc63,
BtiPartitionLocation::DataOffset(63),
"leaf at trie offset 3 must map to Data.db position 63 (UUID 11111111...)"
);
let loc125 =
decode_bti_partition_payload(&trie_data, 7, 8).expect("leaf at offset 6 must decode");
assert_eq!(
loc125,
BtiPartitionLocation::DataOffset(125),
"leaf at trie offset 6 must map to Data.db position 125 (UUID 33333333...)"
);
let result_0 = walk_bti_trie(&trie_data, 17, &[0x40, 0x90]).unwrap();
assert_eq!(
result_0,
Some(BtiPartitionLocation::DataOffset(0)),
"[0x40,0x90] must resolve to DataOffset(0)"
);
let result_63 = walk_bti_trie(&trie_data, 17, &[0x40, 0xBC]).unwrap();
assert_eq!(
result_63,
Some(BtiPartitionLocation::DataOffset(63)),
"[0x40,0xBC] must resolve to DataOffset(63)"
);
let result_125 = walk_bti_trie(&trie_data, 17, &[0x40, 0xF9]).unwrap();
assert_eq!(
result_125,
Some(BtiPartitionLocation::DataOffset(125)),
"[0x40,0xF9] must resolve to DataOffset(125)"
);
use std::io::Cursor;
let raw = std::fs::read(&partitions_db).unwrap();
let mut cursor = Cursor::new(raw.clone());
let r = lookup_partition_in_bti_file(&mut cursor, &[0x40, 0x90]).unwrap();
assert_eq!(r, Some(BtiPartitionLocation::DataOffset(0)));
let mut cursor = Cursor::new(raw.clone());
let r = lookup_partition_in_bti_file(&mut cursor, &[0x40, 0xBC]).unwrap();
assert_eq!(r, Some(BtiPartitionLocation::DataOffset(63)));
let mut cursor = Cursor::new(raw.clone());
let r = lookup_partition_in_bti_file(&mut cursor, &[0x40, 0xF9]).unwrap();
assert_eq!(r, Some(BtiPartitionLocation::DataOffset(125)));
let mut cursor = Cursor::new(raw.clone());
let r = lookup_partition_in_bti_file(&mut cursor, &[0x40, 0x00]).unwrap();
assert_eq!(r, None);
println!(
"VERIFIED: lookup_partition_in_bti_file resolved all 3 BTI partition \
offsets (0, 63, 125) via trie walk, NOT sequential scan"
);
}
}