use crate::db::schema::FieldId;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(in crate::db) struct SchemaVersion(u32);
impl SchemaVersion {
#[must_use]
pub(in crate::db) const fn new(raw: u32) -> Self {
Self(raw)
}
#[must_use]
pub(in crate::db) const fn initial() -> Self {
Self(1)
}
#[must_use]
pub(in crate::db) const fn get(self) -> u32 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(in crate::db) struct SchemaFieldSlot(u16);
impl SchemaFieldSlot {
#[must_use]
pub(in crate::db) const fn new(raw: u16) -> Self {
Self(raw)
}
#[must_use]
pub(in crate::db) fn from_generated_index(index: usize) -> Self {
let slot = u16::try_from(index).expect("generated field slot should fit in persisted u16");
Self(slot)
}
#[must_use]
pub(in crate::db) const fn get(self) -> u16 {
self.0
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct SchemaRowLayout {
version: SchemaVersion,
field_to_slot: Vec<(FieldId, SchemaFieldSlot)>,
retired_field_slots: Vec<(FieldId, SchemaFieldSlot)>,
}
impl SchemaRowLayout {
#[must_use]
pub(in crate::db) const fn new(
version: SchemaVersion,
field_to_slot: Vec<(FieldId, SchemaFieldSlot)>,
) -> Self {
Self::new_with_retired_slots(version, field_to_slot, Vec::new())
}
#[must_use]
pub(in crate::db) const fn new_with_retired_slots(
version: SchemaVersion,
field_to_slot: Vec<(FieldId, SchemaFieldSlot)>,
retired_field_slots: Vec<(FieldId, SchemaFieldSlot)>,
) -> Self {
Self {
version,
field_to_slot,
retired_field_slots,
}
}
#[must_use]
pub(in crate::db) const fn version(&self) -> SchemaVersion {
self.version
}
#[must_use]
pub(in crate::db) const fn field_to_slot(&self) -> &[(FieldId, SchemaFieldSlot)] {
self.field_to_slot.as_slice()
}
#[must_use]
pub(in crate::db) const fn retired_field_slots(&self) -> &[(FieldId, SchemaFieldSlot)] {
self.retired_field_slots.as_slice()
}
#[must_use]
pub(in crate::db) fn next_unallocated_slot(&self) -> SchemaFieldSlot {
let next = self
.field_to_slot()
.iter()
.chain(self.retired_field_slots())
.map(|(_, slot)| slot.get())
.max()
.unwrap_or(0)
.checked_add(1)
.expect("accepted row slots should not be exhausted");
SchemaFieldSlot::new(next)
}
#[must_use]
pub(in crate::db) fn allocated_slot_count(&self) -> usize {
self.field_to_slot()
.iter()
.chain(self.retired_field_slots())
.map(|(_, slot)| usize::from(slot.get()).saturating_add(1))
.max()
.unwrap_or(0)
}
#[must_use]
pub(in crate::db) fn slot_for_field(&self, field_id: FieldId) -> Option<SchemaFieldSlot> {
self.field_to_slot
.iter()
.find_map(|(id, slot)| (*id == field_id).then_some(*slot))
}
#[must_use]
pub(in crate::db) fn clone_retiring_field(&self, field_id: FieldId) -> Option<Self> {
let mut field_to_slot = Vec::with_capacity(self.field_to_slot.len().saturating_sub(1));
let mut retired_field_slots = self.retired_field_slots.clone();
let mut retired = None;
for (id, slot) in &self.field_to_slot {
if *id == field_id {
retired = Some((*id, *slot));
} else {
field_to_slot.push((*id, *slot));
}
}
retired_field_slots.push(retired?);
Some(Self::new_with_retired_slots(
self.version,
field_to_slot,
retired_field_slots,
))
}
}