AkurAI Build
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());
    }
}