Menu
Public repository / master
AkurAI-Framework / crates/sqlexec/src/row.rs
Latest change d971623579ac6cb425eea3ca33f24af5983c18b2 - feat(sqlexec): SQL executor over the storage B+tree (CREATE/INSERT/SELECT) by Ólafur Búi Ólafsson
//! Row encoding: a vector of typed [`Value`]s to bytes and back.
//!
//! A row is stored as a self-describing sequence of cells. Each cell is a
//! one-byte tag followed by its payload, so decoding never needs the schema —
//! though in practice the caller always has it. The column *order* is the
//! schema order; that contract lives with the executor, not here.
use crate::catalog::Reader;
use crate::error::ExecError;
use crate::value::Value;
const TAG_NULL: u8 = 0;
const TAG_INT: u8 = 1;
const TAG_FLOAT: u8 = 2;
const TAG_STR: u8 = 3;
/// Encode an ordered list of cell values into a byte string.
pub fn encode_row(values: &[Value]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&(values.len() as u32).to_be_bytes());
for v in values {
match v {
Value::Null => out.push(TAG_NULL),
Value::Int(i) => {
out.push(TAG_INT);
out.extend_from_slice(&i.to_be_bytes());
}
Value::Float(f) => {
out.push(TAG_FLOAT);
out.extend_from_slice(&f.to_bits().to_be_bytes());
}
Value::Str(s) => {
out.push(TAG_STR);
out.extend_from_slice(&(s.len() as u32).to_be_bytes());
out.extend_from_slice(s.as_bytes());
}
}
}
out
}
/// Decode a row written by [`encode_row`].
pub fn decode_row(bytes: &[u8]) -> Result<Vec<Value>, ExecError> {
let mut r = Reader { bytes, pos: 0 };
let n = r.u32()? as usize;
let mut values = Vec::with_capacity(n);
for _ in 0..n {
let value = match r.byte()? {
TAG_NULL => Value::Null,
TAG_INT => Value::Int(r.u64()? as i64),
TAG_FLOAT => Value::Float(f64::from_bits(r.u64()?)),
TAG_STR => {
let len = r.u32()? as usize;
Value::Str(r.string(len)?)
}
_ => return Err(ExecError::Corrupt("unknown row cell tag")),
};
values.push(value);
}
Ok(values)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn row_round_trips_all_types() {
let row = vec![
Value::Int(-42),
Value::Int(i64::MAX),
Value::Float(3.5),
Value::Str("héllo".into()),
Value::Null,
];
let back = decode_row(&encode_row(&row)).unwrap();
assert_eq!(row, back);
}
#[test]
fn empty_row_round_trips() {
assert_eq!(decode_row(&encode_row(&[])).unwrap(), Vec::<Value>::new());
}
#[test]
fn truncated_bytes_error_not_panic() {
assert!(decode_row(&[0, 0, 0, 1, TAG_INT, 1, 2]).is_err());
}
}