use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BlockDim {
Layer,
Outer,
Page,
Head,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum KvBlockLayout {
UniversalTP,
UniversalPP,
OperationalHND,
OperationalNHD,
Custom([BlockDim; 4]),
#[default]
Unknown,
}
impl KvBlockLayout {
pub fn dim_order(&self) -> Option<[BlockDim; 4]> {
use BlockDim::*;
match self {
Self::UniversalTP => Some([Head, Layer, Outer, Page]),
Self::UniversalPP => Some([Layer, Head, Outer, Page]),
Self::OperationalHND => Some([Layer, Outer, Head, Page]),
Self::OperationalNHD => Some([Layer, Outer, Page, Head]),
Self::Custom(order) => Some(*order),
Self::Unknown => None,
}
}
pub fn requires_transform(&self, other: &Self) -> bool {
match (self.dim_order(), other.dim_order()) {
(Some(a), Some(b)) => a != b,
(None, None) => {
tracing::warn!("Unknown→Unknown KvBlockLayout comparison - this should be fixed");
false
}
_ => true,
}
}
pub fn is_operational(&self) -> bool {
matches!(self, Self::OperationalNHD | Self::OperationalHND)
}
pub fn is_universal(&self) -> bool {
matches!(self, Self::UniversalTP | Self::UniversalPP)
}
pub fn name(&self) -> &'static str {
match self {
Self::UniversalTP => "universal_tp",
Self::UniversalPP => "universal_pp",
Self::OperationalHND => "operational_hnd",
Self::OperationalNHD => "operational_nhd",
Self::Custom(_) => "custom",
Self::Unknown => "unknown",
}
}
pub(crate) fn from_inner_shape(inner_shape: super::InnerShape) -> Self {
match inner_shape {
super::InnerShape::NHD => Self::OperationalNHD,
super::InnerShape::HND => Self::OperationalHND,
super::InnerShape::Unknown => Self::Unknown,
}
}
pub(crate) fn to_inner_shape(self) -> Option<super::InnerShape> {
match self {
Self::OperationalNHD => Some(super::InnerShape::NHD),
Self::OperationalHND => Some(super::InnerShape::HND),
Self::Unknown => Some(super::InnerShape::Unknown),
_ => None,
}
}
}
impl std::fmt::Display for KvBlockLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UniversalTP => write!(f, "Universal TP [nh, nl, no, nt, hd]"),
Self::UniversalPP => write!(f, "Universal PP [nl, nh, no, nt, hd]"),
Self::OperationalHND => write!(f, "Operational HND [nl, no, nh, nt, hd]"),
Self::OperationalNHD => write!(f, "Operational NHD [nl, no, nt, nh, hd]"),
Self::Custom(order) => write!(f, "Custom {:?}", order),
Self::Unknown => write!(f, "Unknown"),
}
}
}
impl std::fmt::Display for BlockDim {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Layer => write!(f, "nl"),
Self::Outer => write!(f, "no"),
Self::Page => write!(f, "nt"),
Self::Head => write!(f, "nh"),
}
}
}
use crate::BlockId;
use crate::layout::PhysicalLayout;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct KvBlocks {
layout: Arc<PhysicalLayout>,
block_ids: Vec<BlockId>,
kv_layout_override: Option<KvBlockLayout>,
}
impl KvBlocks {
pub fn new(
layout: Arc<PhysicalLayout>,
block_ids: Vec<BlockId>,
kv_layout_override: Option<KvBlockLayout>,
) -> anyhow::Result<Self> {
let num_blocks = layout.layout().num_blocks();
for &id in &block_ids {
if id >= num_blocks {
return Err(anyhow::anyhow!(
"Block ID {} out of range (layout has {} blocks)",
id,
num_blocks
));
}
}
if let Some(ref override_layout) = kv_layout_override {
if !layout.layout().is_fully_contiguous() && !override_layout.is_operational() {
return Err(anyhow::anyhow!(
"Layer-separate layouts only support operational block layouts (NHD/HND), got {:?}",
override_layout
));
}
}
let normalized_override = kv_layout_override.and_then(|override_layout| {
if override_layout == layout.layout().block_layout() {
None
} else {
Some(override_layout)
}
});
Ok(Self {
layout,
block_ids,
kv_layout_override: normalized_override,
})
}
#[expect(dead_code)]
pub fn from_layout(
layout: Arc<PhysicalLayout>,
block_ids: Vec<BlockId>,
) -> anyhow::Result<Self> {
Self::new(layout, block_ids, None)
}
#[expect(dead_code)]
pub fn layout(&self) -> &Arc<PhysicalLayout> {
&self.layout
}
#[expect(dead_code)]
pub fn block_ids(&self) -> &[BlockId] {
&self.block_ids
}
pub fn effective_block_layout(&self) -> KvBlockLayout {
self.kv_layout_override
.unwrap_or_else(|| self.layout.layout().block_layout())
}
#[expect(dead_code)]
pub fn layout_override(&self) -> Option<KvBlockLayout> {
self.kv_layout_override
}
#[expect(dead_code)]
pub fn has_override(&self) -> bool {
self.kv_layout_override.is_some()
}
#[expect(dead_code)]
pub fn len(&self) -> usize {
self.block_ids.len()
}
#[expect(dead_code)]
pub fn is_empty(&self) -> bool {
self.block_ids.is_empty()
}
#[expect(dead_code)]
pub fn requires_transform_to(&self, dst: &KvBlocks) -> bool {
self.effective_block_layout()
.requires_transform(&dst.effective_block_layout())
}
}
#[cfg(all(test, feature = "testing-kvbm"))]
mod tests {
use super::*;
#[test]
fn test_dim_order() {
use BlockDim::*;
assert_eq!(
KvBlockLayout::UniversalTP.dim_order(),
Some([Head, Layer, Outer, Page])
);
assert_eq!(
KvBlockLayout::OperationalNHD.dim_order(),
Some([Layer, Outer, Page, Head])
);
assert_eq!(KvBlockLayout::Unknown.dim_order(), None);
}
#[test]
fn test_requires_transform() {
assert!(!KvBlockLayout::OperationalNHD.requires_transform(&KvBlockLayout::OperationalNHD));
assert!(KvBlockLayout::OperationalNHD.requires_transform(&KvBlockLayout::UniversalTP));
assert!(KvBlockLayout::OperationalHND.requires_transform(&KvBlockLayout::OperationalNHD));
assert!(KvBlockLayout::Unknown.requires_transform(&KvBlockLayout::OperationalNHD));
assert!(KvBlockLayout::OperationalNHD.requires_transform(&KvBlockLayout::Unknown));
assert!(!KvBlockLayout::Unknown.requires_transform(&KvBlockLayout::Unknown));
}
#[test]
fn test_is_operational() {
assert!(KvBlockLayout::OperationalNHD.is_operational());
assert!(KvBlockLayout::OperationalHND.is_operational());
assert!(!KvBlockLayout::UniversalTP.is_operational());
assert!(!KvBlockLayout::Unknown.is_operational());
}
#[test]
fn test_is_universal() {
assert!(KvBlockLayout::UniversalTP.is_universal());
assert!(KvBlockLayout::UniversalPP.is_universal());
assert!(!KvBlockLayout::OperationalNHD.is_universal());
}
#[test]
fn test_default() {
assert_eq!(KvBlockLayout::default(), KvBlockLayout::Unknown);
}
#[test]
fn test_serialization() {
let layout = KvBlockLayout::UniversalTP;
let json = serde_json::to_string(&layout).unwrap();
let deserialized: KvBlockLayout = serde_json::from_str(&json).unwrap();
assert_eq!(layout, deserialized);
let custom = KvBlockLayout::Custom([
BlockDim::Head,
BlockDim::Page,
BlockDim::Layer,
BlockDim::Outer,
]);
let json = serde_json::to_string(&custom).unwrap();
let deserialized: KvBlockLayout = serde_json::from_str(&json).unwrap();
assert_eq!(custom, deserialized);
}
#[test]
fn test_inner_shape_conversion() {
use super::super::InnerShape;
assert_eq!(
KvBlockLayout::from_inner_shape(InnerShape::NHD),
KvBlockLayout::OperationalNHD
);
assert_eq!(
KvBlockLayout::from_inner_shape(InnerShape::HND),
KvBlockLayout::OperationalHND
);
assert_eq!(
KvBlockLayout::OperationalNHD.to_inner_shape(),
Some(InnerShape::NHD)
);
assert_eq!(KvBlockLayout::UniversalTP.to_inner_shape(), None);
}
}