use pi_atom::Atom;
use pi_db::{
tables::{mem_ord_table::MemoryOrderedTable, KVTable, TableKV},
Binary, MAX_TABLE_NAME_BYTES,
};
use pi_store::commit_logger::CommitLogger;
type CodecTable = MemoryOrderedTable<usize, CommitLogger>;
const ROOT_TID_BYTES: usize = 16;
const MAX_KEY_BYTES: usize = u16::MAX as usize;
const LARGE_VALUE_BYTES: usize = 70_000;
#[derive(Debug)]
struct ExpectedAction {
key: Vec<u8>,
value: Option<Vec<u8>>,
}
#[test]
fn test_table_wal_codec_valid_layout_and_roundtrip() {
let short_name = "a";
let max_name = "m".repeat(MAX_TABLE_NAME_BYTES);
let short_table = CodecTable::new(Atom::from(short_name), true);
let max_table = CodecTable::new(Atom::from(max_name.clone()), true);
let short_actions = vec![
ExpectedAction {
key: vec![0x11],
value: Some(vec![0x22; LARGE_VALUE_BYTES]),
},
ExpectedAction {
key: vec![0x33; MAX_KEY_BYTES],
value: None,
},
];
let max_actions = vec![ExpectedAction {
key: (0u8..32).collect(),
value: Some(vec![0x44]),
}];
let mut output = vec![0xa5; ROOT_TID_BYTES];
let short_segment_start = output.len();
append_segment(&short_table, &short_actions, &mut output);
let max_segment_start = output.len();
append_segment(&max_table, &max_actions, &mut output);
assert_eq!(&output[..ROOT_TID_BYTES], &[0xa5; ROOT_TID_BYTES]);
assert_segment_bytes(
&output,
short_segment_start,
short_name,
&short_actions,
max_segment_start,
);
assert_segment_bytes(
&output,
max_segment_start,
&max_name,
&max_actions,
output.len(),
);
let (decoded_short_name, short_count, short_actions_offset) =
<CodecTable as KVTable>::get_init_table_prepare_output(
&output,
short_segment_start,
);
assert_eq!(decoded_short_name.as_str(), short_name);
assert_eq!(short_count, short_actions.len() as u64);
let (decoded_short_actions, decoded_max_segment_start) =
<CodecTable as KVTable>::get_all_key_value_from_table_prepare_output(
&output,
&decoded_short_name,
short_count,
short_actions_offset,
);
assert_eq!(decoded_max_segment_start, max_segment_start);
assert_actions(
&decoded_short_actions,
short_name,
&short_actions,
);
let (decoded_max_name, max_count, max_actions_offset) =
<CodecTable as KVTable>::get_init_table_prepare_output(
&output,
decoded_max_segment_start,
);
assert_eq!(decoded_max_name.as_str(), max_name);
assert_eq!(max_count, max_actions.len() as u64);
let (decoded_max_actions, final_offset) =
<CodecTable as KVTable>::get_all_key_value_from_table_prepare_output(
&output,
&decoded_max_name,
max_count,
max_actions_offset,
);
assert_eq!(final_offset, output.len());
assert_actions(&decoded_max_actions, &max_name, &max_actions);
}
fn append_segment(
table: &CodecTable,
actions: &[ExpectedAction],
output: &mut Vec<u8>,
) {
table.init_table_prepare_output(output, actions.len() as u64);
for action in actions {
let key = Binary::from_slice(&action.key);
let value = action
.value
.as_ref()
.map(|value| Binary::from_slice(value));
table.append_key_value_to_table_prepare_output(output, &key, value.as_ref());
}
}
fn assert_segment_bytes(
output: &[u8],
start: usize,
table_name: &str,
actions: &[ExpectedAction],
expected_end: usize,
) {
let mut offset = start;
assert_eq!(read_u16(output, offset) as usize, table_name.len());
offset += 2;
assert_eq!(&output[offset..offset + table_name.len()], table_name.as_bytes());
offset += table_name.len();
assert_eq!(read_u64(output, offset), actions.len() as u64);
offset += 8;
for action in actions {
assert_eq!(read_u16(output, offset) as usize, action.key.len());
offset += 2;
assert_eq!(&output[offset..offset + action.key.len()], action.key.as_slice());
offset += action.key.len();
let value_len = action.value.as_ref().map_or(0, Vec::len);
assert_eq!(read_u32(output, offset) as usize, value_len);
offset += 4;
if let Some(value) = &action.value {
assert!(!value.is_empty(), "valid upsert value must not be empty");
assert_eq!(&output[offset..offset + value.len()], value.as_slice());
offset += value.len();
}
}
assert_eq!(offset, expected_end);
}
fn assert_actions(
actual: &[TableKV],
table_name: &str,
expected: &[ExpectedAction],
) {
assert_eq!(actual.len(), expected.len());
for (index, (actual, expected)) in actual.iter().zip(expected).enumerate() {
assert_eq!(actual.table.as_str(), table_name, "table mismatch at {index}");
assert_eq!(actual.key.as_ref(), expected.key.as_slice(), "key mismatch at {index}");
match (&actual.value, &expected.value) {
(Some(actual), Some(expected)) => {
assert_eq!(actual.as_ref(), expected.as_slice(), "value mismatch at {index}");
assert!(actual.len() > 0, "decoded upsert value must remain nonempty");
},
(None, None) => {}
(actual, expected) => {
panic!("action kind mismatch at {index}: actual={actual:?}, expected={expected:?}");
},
}
}
}
fn read_u16(bytes: &[u8], offset: usize) -> u16 {
u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap())
}
fn read_u32(bytes: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap())
}
fn read_u64(bytes: &[u8], offset: usize) -> u64 {
u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap())
}