use std::io::{Read, Write};
use super::{
io::*,
value::{read_sql_value, write_sql_value},
};
use crate::{row::PRE_MVCC_TXN_ID, Database, Row, StorageError};
const XMAX_TAG_NONE: u8 = 0;
const XMAX_TAG_SOME: u8 = 1;
fn write_row_version_prefix<W: Write>(writer: &mut W, row: &Row) -> Result<(), StorageError> {
write_u64(writer, row.xmin)?;
match row.xmax {
None => write_u8(writer, XMAX_TAG_NONE)?,
Some(xmax) => {
write_u8(writer, XMAX_TAG_SOME)?;
write_u64(writer, xmax)?;
}
}
Ok(())
}
fn read_row_version_prefix<R: Read>(
reader: &mut R,
) -> Result<(u64, Option<u64>), StorageError> {
let xmin = read_u64(reader)?;
let tag = read_u8(reader)?;
let xmax = match tag {
XMAX_TAG_NONE => None,
XMAX_TAG_SOME => Some(read_u64(reader)?),
other => {
return Err(StorageError::IoError(format!(
"Invalid xmax tag in row version prefix: 0x{:02X} (expected 0x00 or 0x01)",
other
)))
}
};
Ok((xmin, xmax))
}
pub fn write_data<W: Write>(writer: &mut W, db: &Database) -> Result<(), StorageError> {
let table_names = db.catalog.list_tables();
for table_name in table_names {
if let Some(table) = db.get_table(&table_name) {
write_string(writer, &table_name)?;
write_u64(writer, table.row_count() as u64)?;
for (_idx, row) in table.scan_live() {
write_row_version_prefix(writer, row)?;
for value in &row.values {
write_sql_value(writer, value)?;
}
}
}
}
Ok(())
}
pub fn read_data<R: Read>(
reader: &mut R,
db: &mut Database,
version: u8,
) -> Result<(), StorageError> {
let table_count = db.catalog.list_tables().len();
let mut loaded_tables = Vec::with_capacity(table_count);
let has_mvcc_prefix = version >= 7;
for _ in 0..table_count {
let table_name = read_string(reader)?;
let row_count = read_u64(reader)?;
let column_count = db
.get_table(&table_name)
.map(|t| t.schema.columns.len())
.ok_or_else(|| StorageError::TableNotFound(table_name.clone()))?;
if let Some(table) = db.get_table_mut(&table_name) {
for _ in 0..row_count {
let (xmin, xmax) = if has_mvcc_prefix {
read_row_version_prefix(reader)?
} else {
(PRE_MVCC_TXN_ID, None)
};
let mut values = Vec::with_capacity(column_count);
for _ in 0..column_count {
values.push(read_sql_value(reader)?);
}
let mut row = Row::from_vec(values);
row.xmin = xmin;
row.xmax = xmax;
table.insert(row).map_err(|e| {
StorageError::NotImplemented(format!("Failed to insert row: {}", e))
})?;
}
}
loaded_tables.push(table_name);
}
for table_name in loaded_tables {
db.rebuild_indexes(&table_name);
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use vibesql_types::SqlValue;
use super::*;
#[test]
fn test_row_version_prefix_roundtrip_live_row() {
let row = {
let mut r = Row::from_vec(vec![SqlValue::Integer(42)]);
r.xmin = 1234;
r.xmax = None;
r
};
let mut buf = Vec::new();
write_row_version_prefix(&mut buf, &row).unwrap();
let mut reader = Cursor::new(&buf);
let (xmin, xmax) = read_row_version_prefix(&mut reader).unwrap();
assert_eq!(xmin, 1234);
assert_eq!(xmax, None);
}
#[test]
fn test_row_version_prefix_roundtrip_deleted_row() {
let row = {
let mut r = Row::from_vec(vec![SqlValue::Integer(42)]);
r.xmin = 100;
r.xmax = Some(200);
r
};
let mut buf = Vec::new();
write_row_version_prefix(&mut buf, &row).unwrap();
let mut reader = Cursor::new(&buf);
let (xmin, xmax) = read_row_version_prefix(&mut reader).unwrap();
assert_eq!(xmin, 100);
assert_eq!(xmax, Some(200));
}
#[test]
fn test_row_version_prefix_invalid_tag() {
let mut buf = Vec::new();
write_u64(&mut buf, 1).unwrap(); write_u8(&mut buf, 0xFF).unwrap();
let mut reader = Cursor::new(&buf);
let result = read_row_version_prefix(&mut reader);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid xmax tag"));
}
#[test]
fn test_row_version_prefix_byte_layout_live() {
let row = {
let mut r = Row::from_vec(vec![]);
r.xmin = 0xDEAD_BEEF_CAFE_F00D;
r.xmax = None;
r
};
let mut buf = Vec::new();
write_row_version_prefix(&mut buf, &row).unwrap();
assert_eq!(buf.len(), 9);
assert_eq!(buf[8], XMAX_TAG_NONE);
}
#[test]
fn test_row_version_prefix_byte_layout_deleted() {
let row = {
let mut r = Row::from_vec(vec![]);
r.xmin = 1;
r.xmax = Some(2);
r
};
let mut buf = Vec::new();
write_row_version_prefix(&mut buf, &row).unwrap();
assert_eq!(buf.len(), 17);
assert_eq!(buf[8], XMAX_TAG_SOME);
}
}