Skip to main content

nodedb_wal/record/
aborted.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Write-abort payload: names a previously appended record that must never be
4//! replayed.
5//!
6//! A forward write record is appended before the executing engine has decided
7//! whether to accept the write. When the verdict is a refusal, the record is
8//! already in the log and replay would resurrect a write the server told the
9//! client it refused. The abort record names that record's LSN so the replay
10//! pre-pass can drop it.
11//!
12//! Payload layout (fixed 8 bytes, little-endian):
13//!
14//! ```text
15//! ┌────────────────┐
16//! │ aborted_lsn u64│
17//! └────────────────┘
18//! ```
19
20use crate::error::{Result, WalError};
21
22/// Size of a write-abort payload on disk.
23pub const WRITE_ABORTED_PAYLOAD_SIZE: usize = 8;
24
25/// Names the LSN of a forward write record that must not be replayed.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct WriteAbortedPayload {
28    /// LSN of the record this abort invalidates.
29    pub aborted_lsn: u64,
30}
31
32impl WriteAbortedPayload {
33    pub const fn new(aborted_lsn: u64) -> Self {
34        Self { aborted_lsn }
35    }
36
37    pub fn to_bytes(&self) -> [u8; WRITE_ABORTED_PAYLOAD_SIZE] {
38        self.aborted_lsn.to_le_bytes()
39    }
40
41    pub fn from_bytes(buf: &[u8]) -> Result<Self> {
42        if buf.len() != WRITE_ABORTED_PAYLOAD_SIZE {
43            return Err(WalError::InvalidPayload {
44                detail: format!(
45                    "WriteAborted payload must be {WRITE_ABORTED_PAYLOAD_SIZE} bytes, got {}",
46                    buf.len()
47                ),
48            });
49        }
50        let aborted_lsn = u64::from_le_bytes([
51            buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
52        ]);
53        Ok(Self { aborted_lsn })
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn write_aborted_roundtrip() {
63        let payload = WriteAbortedPayload::new(9_876_543_210);
64        let bytes = payload.to_bytes();
65        assert_eq!(WriteAbortedPayload::from_bytes(&bytes).unwrap(), payload);
66    }
67
68    #[test]
69    fn write_aborted_wrong_size_rejected() {
70        assert!(WriteAbortedPayload::from_bytes(&[0u8; 7]).is_err());
71        assert!(WriteAbortedPayload::from_bytes(&[0u8; 9]).is_err());
72    }
73}