armdb 0.14.2

sharded bitcask key-value storage optimized for NVMe
Documentation
use armour_core::{Fields, ScalarTyp, Typ};

use crate::codec::InspectionEncoding;

pub(crate) fn typ_fixed_width(typ: &Typ) -> Option<usize> {
    match typ {
        Typ::Scalar(scalar) => match scalar {
            ScalarTyp::Void => Some(0),
            ScalarTyp::Bool | ScalarTyp::U8 => Some(1),
            ScalarTyp::U16 => Some(2),
            ScalarTyp::U32 | ScalarTyp::I32 | ScalarTyp::F32 | ScalarTyp::Id32 => Some(4),
            ScalarTyp::U64
            | ScalarTyp::U64BE
            | ScalarTyp::TimestampBE
            | ScalarTyp::I64
            | ScalarTyp::F64
            | ScalarTyp::Datetime
            | ScalarTyp::Timestamp
            | ScalarTyp::Id64
            | ScalarTyp::Fuid => Some(8),
            ScalarTyp::Decimal => Some(16),
            ScalarTyp::ArrayBytes(n) => usize::try_from(*n).ok(),
            ScalarTyp::LowId
            | ScalarTyp::Str
            | ScalarTyp::Bytes
            | ScalarTyp::RustJson
            | ScalarTyp::JsonBytes => None,
        },
        Typ::Array(n, inner) => {
            let inner = typ_fixed_width(inner)?;
            let inner_u32 = u32::try_from(inner).ok()?;
            n.checked_mul(inner_u32)
                .and_then(|width| usize::try_from(width).ok())
        }
        Typ::Struct(s) => {
            let mut total = 0usize;
            match s.fields {
                Fields::Named(fields) => {
                    for (_, field_ty) in fields {
                        total = total.checked_add(typ_fixed_width(field_ty)?)?;
                    }
                }
                Fields::Unnamed(fields) => {
                    for field_ty in fields {
                        total = total.checked_add(typ_fixed_width(field_ty)?)?;
                    }
                }
            }
            Some(total)
        }
        Typ::Vec(_) | Typ::Optional(_) | Typ::SimpleEnum(_) | Typ::Enum(_) | Typ::Custom(_, _) => {
            None
        }
    }
}

pub(crate) fn zero_inspection_encoding(ty: &Typ, value_width: usize) -> InspectionEncoding {
    if cfg!(target_endian = "little") && typ_fixed_width(ty) == Some(value_width) {
        InspectionEncoding::Rapira
    } else {
        InspectionEncoding::RawLayout
    }
}

#[cfg(test)]
mod tests {
    use armour_core::{EnumType, Fields, ScalarTyp, SimpleEnumType, StructType, Typ};

    use crate::codec::InspectionEncoding;

    use super::{typ_fixed_width, zero_inspection_encoding};

    #[test]
    fn scalar_widths() {
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Void)), Some(0));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Bool)), Some(1));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::U8)), Some(1));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::U16)), Some(2));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::U32)), Some(4));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::I32)), Some(4));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::F32)), Some(4));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Id32)), Some(4));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::U64)), Some(8));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::I64)), Some(8));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::F64)), Some(8));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Datetime)), Some(8));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Timestamp)), Some(8));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Id64)), Some(8));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Fuid)), Some(8));
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::U64BE)), Some(8));
        assert_eq!(
            typ_fixed_width(&Typ::Scalar(ScalarTyp::TimestampBE)),
            Some(8)
        );
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Decimal)), Some(16));
        assert_eq!(
            typ_fixed_width(&Typ::Scalar(ScalarTyp::ArrayBytes(7))),
            Some(7)
        );
    }

    #[test]
    fn low_id_and_variable_shapes_are_none() {
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::LowId)), None);
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Str)), None);
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::Bytes)), None);
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::RustJson)), None);
        assert_eq!(typ_fixed_width(&Typ::Scalar(ScalarTyp::JsonBytes)), None);
        assert_eq!(
            typ_fixed_width(&Typ::Vec(&Typ::Scalar(ScalarTyp::U8))),
            None
        );
        assert_eq!(
            typ_fixed_width(&Typ::Optional(&Typ::Scalar(ScalarTyp::U8))),
            None
        );
        assert_eq!(
            typ_fixed_width(&Typ::SimpleEnum(SimpleEnumType {
                name: "E",
                variants: &[(0, "A")],
            })),
            None
        );
        assert_eq!(
            typ_fixed_width(&Typ::Enum(EnumType {
                name: "E",
                variants: &[(0, ("A", Typ::Scalar(ScalarTyp::U8)))],
            })),
            None
        );
        assert_eq!(typ_fixed_width(&Typ::Custom("X", &[])), None);
    }

    #[test]
    fn nested_array_and_struct() {
        const INNER: Typ = Typ::Scalar(ScalarTyp::U16);
        assert_eq!(typ_fixed_width(&Typ::Array(3, &INNER)), Some(6));
        assert_eq!(
            typ_fixed_width(&Typ::Struct(StructType {
                name: "S",
                fields: Fields::Named(&[
                    ("a", Typ::Scalar(ScalarTyp::Id32)),
                    ("b", Typ::Scalar(ScalarTyp::U64)),
                ]),
            })),
            Some(12)
        );
        assert_eq!(
            typ_fixed_width(&Typ::Struct(StructType {
                name: "T",
                fields: Fields::Unnamed(&[Typ::Scalar(ScalarTyp::U8), Typ::Array(2, &INNER),]),
            })),
            Some(5)
        );
    }

    #[test]
    fn unsupported_nested_and_overflow_return_none() {
        assert_eq!(
            typ_fixed_width(&Typ::Array(2, &Typ::Scalar(ScalarTyp::Str))),
            None
        );
        assert_eq!(
            typ_fixed_width(&Typ::Struct(StructType {
                name: "Bad",
                fields: Fields::Named(&[("x", Typ::Scalar(ScalarTyp::LowId))]),
            })),
            None
        );
        assert_eq!(
            typ_fixed_width(&Typ::Array(u32::MAX, &Typ::Scalar(ScalarTyp::U64))),
            None
        );
    }

    #[test]
    fn zero_encoding_rapira_when_width_matches_on_little_endian() {
        let ty = Typ::Scalar(ScalarTyp::U32);
        let encoding = zero_inspection_encoding(&ty, 4);
        if cfg!(target_endian = "little") {
            assert_eq!(encoding, InspectionEncoding::Rapira);
        } else {
            assert_eq!(encoding, InspectionEncoding::RawLayout);
        }
    }

    #[test]
    fn zero_encoding_raw_layout_on_width_mismatch() {
        let ty = Typ::Scalar(ScalarTyp::U32); // width 4
        assert_eq!(
            zero_inspection_encoding(&ty, 8),
            InspectionEncoding::RawLayout
        );
    }
}