use bitfield_struct::bitfield;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum PkColType {
Bytes = 0,
Uuid = 1,
Text = 2,
I64 = 3,
}
impl PkColType {
pub const fn wire_encoding(&self) -> WireEncoding {
match self {
Self::Bytes | Self::Text => WireEncoding::LengthPrefixed,
Self::Uuid => WireEncoding::Fixed16,
Self::I64 => WireEncoding::ZigzagVarint,
}
}
const fn from_bits(value: u8) -> Self {
match value & 0b11 {
0 => Self::Bytes,
1 => Self::Uuid,
2 => Self::Text,
_ => Self::I64,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum SysColType {
Bytes = 0,
Text = 1,
I64 = 2,
Uuid = 3,
MaxI64 = 4,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WireEncoding {
LengthPrefixed,
Fixed16,
ZigzagVarint,
}
impl SysColType {
pub const fn wire_encoding(&self) -> WireEncoding {
match self {
Self::Bytes | Self::Text => WireEncoding::LengthPrefixed,
Self::Uuid => WireEncoding::Fixed16,
Self::I64 | Self::MaxI64 => WireEncoding::ZigzagVarint,
}
}
const fn into_bits(self) -> u8 {
self as _
}
pub const fn try_from_bits(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Bytes),
1 => Some(Self::Text),
2 => Some(Self::I64),
3 => Some(Self::Uuid),
4 => Some(Self::MaxI64),
_ => None,
}
}
const fn from_bits(value: u8) -> Self {
match value {
0 => Self::Bytes,
1 => Self::Text,
2 => Self::I64,
3 => Self::Uuid,
4 => Self::MaxI64,
_ => panic!("invalid SysColType"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SysTableId(u16);
impl SysTableId {
pub const fn new(pk_types: &[PkColType], index: u16) -> Self {
let count = pk_types.len();
assert!(count >= 1 && count <= 4, "PK column count must be 1-4");
let index_bits = Self::index_bits_for(count);
assert!(
(index as u32) < (1u32 << index_bits),
"table index exceeds available bits for this PK count"
);
let mut raw = (((count - 1) as u16) << 14) | index;
let mut i = 0;
while i < count {
raw |= (pk_types[i] as u16) << (12 - 2 * i);
i += 1;
}
Self(raw)
}
pub const fn from_raw(raw: u16) -> Self {
Self(raw)
}
pub const fn raw(&self) -> u16 {
self.0
}
pub const fn pk_count(&self) -> usize {
((self.0 >> 14) as usize) + 1
}
pub const fn pk_col_type(&self, i: usize) -> PkColType {
assert!(i < self.pk_count(), "PK column index out of range");
PkColType::from_bits(((self.0 >> (12 - 2 * i)) & 0b11) as u8)
}
pub const fn index_bits(&self) -> u32 {
Self::index_bits_for(self.pk_count())
}
pub const fn index(&self) -> u16 {
self.0 & ((1u16 << self.index_bits()) - 1)
}
const fn index_bits_for(pk_count: usize) -> u32 {
14 - 2 * pk_count as u32
}
}
impl From<u16> for SysTableId {
fn from(raw: u16) -> Self {
Self(raw)
}
}
impl From<SysTableId> for u16 {
fn from(id: SysTableId) -> u16 {
id.0
}
}
impl core::fmt::Debug for SysTableId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "SysTableId(0x{:04X}, pk=[", self.0)?;
let mut i = 0;
while i < self.pk_count() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{:?}", self.pk_col_type(i))?;
i += 1;
}
write!(f, "], index={})", self.index())
}
}
#[bitfield(u8)]
#[derive(PartialEq, Eq, Hash)]
pub struct SysColumnId {
#[bits(5)]
pub index: u8,
#[bits(3)]
pub col_type: SysColType,
}
impl SysColumnId {
pub fn try_from_raw(raw: u8) -> Option<Self> {
let type_bits = (raw >> 5) & 0x07;
SysColType::try_from_bits(type_bits)?;
Some(Self::from(raw))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn table_id_round_trip_all_pk_counts() {
let shapes: [&[PkColType]; 4] = [
&[PkColType::Uuid],
&[PkColType::Bytes, PkColType::Uuid],
&[PkColType::Text, PkColType::I64, PkColType::Uuid],
&[
PkColType::Bytes,
PkColType::Text,
PkColType::I64,
PkColType::Uuid,
],
];
for (n, pk_types) in shapes.iter().enumerate() {
let id = SysTableId::new(pk_types, 5);
let recovered = SysTableId::from_raw(id.raw());
assert_eq!(recovered, id);
assert_eq!(id.pk_count(), n + 1);
for (i, expected) in pk_types.iter().enumerate() {
assert_eq!(id.pk_col_type(i), *expected);
}
assert_eq!(id.index(), 5);
}
}
#[test]
fn table_id_index_in_low_bits() {
let id = SysTableId::new(&[PkColType::Bytes], 1);
assert_eq!(id.raw(), 1);
}
#[test]
fn table_id_pk_shape_in_high_bits() {
let id = SysTableId::new(&[PkColType::Uuid], 0);
assert_eq!(id.raw() >> 12, 0b00_01);
let id = SysTableId::new(&[PkColType::Text, PkColType::I64], 0);
assert_eq!(id.raw() >> 10, 0b01_10_11);
}
#[test]
fn table_id_index_headroom_per_pk_count() {
let one = SysTableId::new(&[PkColType::Uuid], 4095);
assert_eq!((one.index_bits(), one.index()), (12, 4095));
let two = SysTableId::new(&[PkColType::Uuid; 2], 1023);
assert_eq!((two.index_bits(), two.index()), (10, 1023));
let three = SysTableId::new(&[PkColType::Uuid; 3], 255);
assert_eq!((three.index_bits(), three.index()), (8, 255));
let four = SysTableId::new(&[PkColType::Uuid; 4], 63);
assert_eq!((four.index_bits(), four.index()), (6, 63));
}
#[test]
#[should_panic(expected = "table index exceeds available bits")]
fn table_id_rejects_index_overflow() {
let _ = SysTableId::new(&[PkColType::Uuid], 4096);
}
#[test]
fn table_id_every_bit_pattern_is_valid() {
for raw in 0..=u16::MAX {
let id = SysTableId::from_raw(raw);
let count = id.pk_count();
assert!((1..=4).contains(&count));
for i in 0..count {
let _ = id.pk_col_type(i);
}
assert!(id.index() < (1 << id.index_bits()));
assert_eq!(id.raw(), raw);
}
}
#[test]
fn pk_type_wire_encodings() {
assert_eq!(
PkColType::Bytes.wire_encoding(),
WireEncoding::LengthPrefixed
);
assert_eq!(
PkColType::Text.wire_encoding(),
WireEncoding::LengthPrefixed
);
assert_eq!(PkColType::Uuid.wire_encoding(), WireEncoding::Fixed16);
assert_eq!(PkColType::I64.wire_encoding(), WireEncoding::ZigzagVarint);
}
#[test]
fn column_id_round_trip() {
let id = SysColumnId::new()
.with_index(3)
.with_col_type(SysColType::Text);
assert_eq!(id.index(), 3);
assert_eq!(id.col_type(), SysColType::Text);
let raw: u8 = id.into();
let recovered = SysColumnId::from(raw);
assert_eq!(recovered, id);
}
#[test]
fn column_id_index_in_low_bits() {
let id = SysColumnId::new()
.with_index(1)
.with_col_type(SysColType::Bytes);
let raw: u8 = id.into();
assert_eq!(raw, 1);
}
#[test]
fn column_id_type_in_high_bits() {
let id = SysColumnId::new()
.with_index(0)
.with_col_type(SysColType::I64);
let raw: u8 = id.into();
assert_eq!(raw >> 5, 0b010);
let id = SysColumnId::new()
.with_index(0)
.with_col_type(SysColType::Text);
let raw: u8 = id.into();
assert_eq!(raw >> 5, 0b001);
}
#[test]
fn invalid_col_type_detected() {
assert!(SysColType::try_from_bits(4).is_some()); assert!(SysColType::try_from_bits(5).is_none());
assert!(SysColType::try_from_bits(6).is_none());
assert!(SysColType::try_from_bits(7).is_none());
assert!(SysColumnId::try_from_raw(0b101_00000).is_none());
assert!(SysColumnId::try_from_raw(0b100_00011).is_some());
}
#[test]
fn table_id_const_constructible() {
const SETTINGS: SysTableId = SysTableId::new(&[PkColType::Text], 7);
assert_eq!(SETTINGS.pk_count(), 1);
assert_eq!(SETTINGS.pk_col_type(0), PkColType::Text);
assert_eq!(SETTINGS.index(), 7);
}
}