Skip to main content

lsm_tree/
format_version.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-present, fjall-rs
3// Copyright (c) 2026-present, Structured World Foundation
4
5/// Block / SST disk format version.
6///
7/// This enum tracks the on-disk layout of Blocks and SST files: block
8/// header layout, filter wire format, range-tombstone encoding, ECC
9/// trailer geometry. It is the version persisted in the manifest's
10/// `format_version` section and gated at `Tree::open`.
11///
12/// ## Relationship to the manifest layout version
13///
14/// `FormatVersion` and [`crate::manifest_blocks::MANIFEST_LAYOUT_VERSION_V1`]
15/// evolve at **independent cadences**:
16///
17/// | Concept | Type | Tracks |
18/// |---------|------|--------|
19/// | `FormatVersion` | This enum (V1..V5) | Block / SST on-disk layout |
20/// | `manifest_layout_version` | `u8` in manifest Footer Block | Manifest file structure (footer fields, TOC encoding, head-mirror geometry) |
21///
22/// A block format bump does NOT force a manifest layout bump and
23/// vice versa. The CURRENT pointer's canonical digest binds the
24/// manifest layout version (so a manifest-only break is detected
25/// at recovery), and the manifest's `format_version` section binds
26/// this enum (so a block-format-only break is detected at
27/// `Tree::open`).
28///
29/// ## Amendment policy
30///
31/// Once a value is **published to crates.io** (any released binary
32/// writes that value to disk), **any** subsequent change to the
33/// on-disk bytes under that value is a breaking change that MUST
34/// bump to a new variant. This applies regardless of whether the
35/// change is otherwise additive: a reader running the old code is
36/// not free to interpret unknown bytes.
37///
38/// The amendment window is the **pre-release period**: while a
39/// `FormatVersion` is being actively developed and no published
40/// binary writes it, the on-disk bytes under that version MAY be
41/// amended in place (no enum bump required). The release that
42/// crystallises the variant ends this window.
43///
44/// Same rule applies to `manifest_layout_version` independently:
45/// pre-publication amendments are free; post-publication changes
46/// require a new layout-version constant.
47///
48/// **Practical checklist for any PR that touches on-disk bytes:**
49///
50/// 1. Identify which layer the change touches (Block/SST → this
51///    enum; manifest framing → `manifest_layout_version`).
52/// 2. If that layer's current value has shipped to crates.io,
53///    add a new variant / constant instead of amending in place.
54/// 3. The OTHER layer's value stays unless its layer also changed.
55#[derive(Copy, Clone, Debug, Eq, PartialEq)]
56pub enum FormatVersion {
57    /// Version for 1.x.x releases
58    V1 = 1,
59
60    /// Version for 2.x.x releases
61    V2,
62
63    /// Version for 3.x.x releases
64    V3,
65
66    /// Version for range-tombstone SST semantics
67    V4,
68
69    /// Two on-disk changes shipped together in this format version
70    /// (V5 had not been released when both landed, so they collapse
71    /// into the same version bump):
72    ///
73    /// 1. `BuRR` (Bumped Ribbon Retrieval) filter wire format. Filter
74    ///    blocks are no longer Bloom-encoded; the `filter_type` byte +
75    ///    per-layer header layout is documented in
76    ///    `src/table/filter/ribbon/burr/wire.rs`.
77    ///
78    /// 2. Per-block transform flags + Page ECC. The self-describing block
79    ///    types (`Meta` / `Manifest` / `ManifestFooter`) carry a
80    ///    `block_flags: u8` byte with the transform-presence bits;
81    ///    `ECC_PARITY` marks that a Reed-Solomon parity trailer follows
82    ///    the XXH3-covered payload (its length is derived from
83    ///    `data_length`, not stored). SST block types (`Data` / `Index` /
84    ///    `Filter` / `RangeTombstone`) keep the compact header WITHOUT this
85    ///    byte: their parity / per-KV-footer presence is a per-SST property
86    ///    read from the table descriptor (`page_ecc` / `kv_checksum_algo`),
87    ///    not a serialized header flag. `KV_CHECKSUM_FOOTER` (set on the
88    ///    self-describing types) marks a per-entry checksum footer.
89    ///    When `Config::page_ecc(false)` (the default) no parity bytes
90    ///    follow; likewise no footer unless per-KV checksums are enabled.
91    ///    The block
92    ///    magic was bumped to `[L,S,M,4]` (was `[L,S,M,3]` on pre-V5
93    ///    versions) so a pre-V5 reader that bypasses the manifest gate
94    ///    fails fast at block header decode rather than misreading the
95    ///    new layout.
96    ///
97    /// V3 / V4 ↔ V5 incompatibility is enforced primarily by the
98    /// manifest version gate at `Tree::open` (returns
99    /// `InvalidVersion` for anything other than V5).
100    V5,
101}
102
103impl core::fmt::Display for FormatVersion {
104    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105        write!(f, "{}", u8::from(*self))
106    }
107}
108
109impl From<FormatVersion> for u8 {
110    fn from(value: FormatVersion) -> Self {
111        match value {
112            FormatVersion::V1 => 1,
113            FormatVersion::V2 => 2,
114            FormatVersion::V3 => 3,
115            FormatVersion::V4 => 4,
116            FormatVersion::V5 => 5,
117        }
118    }
119}
120
121impl TryFrom<u8> for FormatVersion {
122    type Error = ();
123
124    fn try_from(value: u8) -> Result<Self, Self::Error> {
125        match value {
126            1 => Ok(Self::V1),
127            2 => Ok(Self::V2),
128            3 => Ok(Self::V3),
129            4 => Ok(Self::V4),
130            5 => Ok(Self::V5),
131            _ => Err(()),
132        }
133    }
134}