use super::*;
pub(super) fn encode_wal_payload(table: &str, rid: RowId, row_bytes: &[u8]) -> Vec<u8> {
let name = table.as_bytes();
let mut out = Vec::with_capacity(4 + name.len() + 4 + 2 + 4 + row_bytes.len());
out.extend_from_slice(&(name.len() as u32).to_le_bytes());
out.extend_from_slice(name);
out.extend_from_slice(&rid.page_id.to_le_bytes());
out.extend_from_slice(&rid.slot_index.to_le_bytes());
out.extend_from_slice(&(row_bytes.len() as u32).to_le_bytes());
out.extend_from_slice(row_bytes);
out
}
pub(super) fn decode_wal_payload(data: &[u8]) -> Option<(String, RowId, Vec<u8>)> {
let mut pos = 0usize;
if data.len() < 4 {
return None;
}
let name_len = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
if pos + name_len > data.len() {
return None;
}
let name = std::str::from_utf8(&data[pos..pos + name_len])
.ok()?
.to_string();
pos += name_len;
if pos + 4 + 2 + 4 > data.len() {
return None;
}
let page_id = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?);
pos += 4;
let slot_index = u16::from_le_bytes(data[pos..pos + 2].try_into().ok()?);
pos += 2;
let row_len = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
if pos + row_len > data.len() {
return None;
}
let row_bytes = data[pos..pos + row_len].to_vec();
Some((
name,
RowId {
page_id,
slot_index,
},
row_bytes,
))
}
pub(super) fn write_overflow_chain_logged(
heap: &mut HeapFile,
wal: &mut Wal,
table: &str,
tx_id: u64,
value: &[u8],
) -> io::Result<OverflowStub> {
if value.len() > MAX_VALUE_SIZE {
return Err(StorageError::ValueTooLarge {
size: value.len(),
max: MAX_VALUE_SIZE,
}
.into());
}
let n = value.len().div_ceil(OVERFLOW_PAYLOAD_CAP).max(1);
let mut pages = Vec::with_capacity(n);
for _ in 0..n {
pages.push(heap.allocate_overflow_page()?);
}
for i in 0..n {
let start = i * OVERFLOW_PAYLOAD_CAP;
let end = (start + OVERFLOW_PAYLOAD_CAP).min(value.len());
let chunk = &value[start..end];
let next = if i + 1 < n {
pages[i + 1]
} else {
OVERFLOW_CHAIN_END
};
let payload = encode_overflow_write_payload(table, pages[i], next, chunk);
wal.append(tx_id, WalRecordType::OverflowWrite, &payload)?;
let lsn = wal.last_appended_lsn();
heap.write_overflow_page(pages[i], next, chunk, lsn)?;
}
Ok(OverflowStub::new(
value.len() as u64,
pages[0],
crc32fast::hash(value),
))
}
pub(super) fn encode_row_with_spill_logged(
tbl: &mut Table,
wal: &mut Wal,
tx_id: u64,
values: &Row,
) -> io::Result<Vec<u8>> {
let v1_len = crate::row::v1_encoded_len(tbl.row_layout(), values);
let is_indexed = tbl.indexed_col_mask();
let chosen = plan_spill(tbl.row_layout(), values, v1_len, &is_indexed);
if chosen.is_empty() {
let mut v1 = Vec::new();
encode_row_into(&tbl.schema, values, &mut v1);
return Ok(v1);
}
let table_name = tbl.schema.table_name.clone();
let n_var = tbl.row_layout().n_var();
let mut spilled: Vec<Option<OverflowStub>> = vec![None; n_var];
for col_idx in chosen {
let var_idx = tbl
.row_layout()
.var_index(col_idx)
.expect("plan_spill only returns var columns");
let bytes: Vec<u8> = match &values[col_idx] {
Value::Str(s) => s.as_bytes().to_vec(),
Value::Bytes(b) => b.to_vec(),
Value::Json(b) => b.to_vec(),
_ => continue,
};
let stub = write_overflow_chain_logged(&mut tbl.heap, wal, &table_name, tx_id, &bytes)?;
spilled[var_idx] = Some(stub);
}
let mut out = Vec::new();
encode_row_v2_into(&tbl.schema, tbl.row_layout(), values, &spilled, &mut out);
Ok(out)
}
pub(super) fn encode_overflow_write_payload(
table: &str,
page_id: u32,
next_page: u32,
chunk: &[u8],
) -> Vec<u8> {
let name = table.as_bytes();
let mut out = Vec::with_capacity(2 + name.len() + 4 + 4 + 2 + chunk.len());
out.extend_from_slice(&(name.len() as u16).to_le_bytes());
out.extend_from_slice(name);
out.extend_from_slice(&page_id.to_le_bytes());
out.extend_from_slice(&next_page.to_le_bytes());
out.extend_from_slice(&(chunk.len() as u16).to_le_bytes());
out.extend_from_slice(chunk);
out
}
pub(super) fn decode_overflow_write_payload(data: &[u8]) -> Option<(String, u32, u32, Vec<u8>)> {
let mut pos = 0usize;
if data.len() < 2 {
return None;
}
let name_len = u16::from_le_bytes(data[pos..pos + 2].try_into().ok()?) as usize;
pos += 2;
if pos + name_len + 4 + 4 + 2 > data.len() {
return None;
}
let name = std::str::from_utf8(&data[pos..pos + name_len])
.ok()?
.to_string();
pos += name_len;
let page_id = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?);
pos += 4;
let next_page = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?);
pos += 4;
let chunk_len = u16::from_le_bytes(data[pos..pos + 2].try_into().ok()?) as usize;
pos += 2;
if pos + chunk_len > data.len() {
return None;
}
Some((
name,
page_id,
next_page,
data[pos..pos + chunk_len].to_vec(),
))
}
pub(super) fn encode_overflow_free_payload(table: &str, pages: &[u32]) -> Vec<u8> {
let name = table.as_bytes();
let mut out = Vec::with_capacity(2 + name.len() + 4 + pages.len() * 4);
out.extend_from_slice(&(name.len() as u16).to_le_bytes());
out.extend_from_slice(name);
out.extend_from_slice(&(pages.len() as u32).to_le_bytes());
for p in pages {
out.extend_from_slice(&p.to_le_bytes());
}
out
}
pub(super) fn decode_overflow_free_payload(data: &[u8]) -> Option<(String, Vec<u32>)> {
let mut pos = 0usize;
if data.len() < 2 {
return None;
}
let name_len = u16::from_le_bytes(data[pos..pos + 2].try_into().ok()?) as usize;
pos += 2;
if pos + name_len + 4 > data.len() {
return None;
}
let name = std::str::from_utf8(&data[pos..pos + name_len])
.ok()?
.to_string();
pos += name_len;
let count = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
if pos + count * 4 > data.len() {
return None;
}
let mut pages = Vec::with_capacity(count);
for _ in 0..count {
pages.push(u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?));
pos += 4;
}
Some((name, pages))
}