LCOV - code coverage report
Current view: top level - libs/wal_decoder/src/models - value.rs (source / functions) Coverage Total Hit
Test: 1e20c4f2b28aa592527961bb32170ebbd2c9172f.info Lines: 91.1 % 135 123
Test Date: 2025-07-16 12:29:03 Functions: 81.8 % 11 9

            Line data    Source code
       1              : //! This module defines the value type used by the storage engine.
       2              : //!
       3              : //! A [`Value`] represents either a completely new value for one Key ([`Value::Image`]),
       4              : //! or a "delta" of how to get from previous version of the value to the new one
       5              : //! ([`Value::WalRecord`]])
       6              : //!
       7              : //! Note that the [`Value`] type is used for the permananent storage format, so any
       8              : //! changes to it must be backwards compatible.
       9              : 
      10              : use bytes::Bytes;
      11              : use serde::{Deserialize, Serialize};
      12              : 
      13              : use crate::models::record::NeonWalRecord;
      14              : 
      15            0 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
      16              : pub enum Value {
      17              :     /// An Image value contains a full copy of the value
      18              :     Image(Bytes),
      19              :     /// A WalRecord value contains a WAL record that needs to be
      20              :     /// replayed get the full value. Replaying the WAL record
      21              :     /// might need a previous version of the value (if will_init()
      22              :     /// returns false), or it may be replayed stand-alone (true).
      23              :     WalRecord(NeonWalRecord),
      24              : }
      25              : 
      26              : impl Value {
      27              :     #[inline(always)]
      28          402 :     pub fn is_image(&self) -> bool {
      29          402 :         matches!(self, Value::Image(_))
      30            0 :     }
      31              : 
      32              :     #[inline(always)]
      33      3620363 :     pub fn will_init(&self) -> bool {
      34      3620363 :         match self {
      35      3516853 :             Value::Image(_) => true,
      36       103510 :             Value::WalRecord(rec) => rec.will_init(),
      37              :         }
      38      2565282 :     }
      39              : 
      40              :     #[inline(always)]
      41          437 :     pub fn estimated_size(&self) -> usize {
      42            0 :         match self {
      43          314 :             Value::Image(image) => image.len(),
      44              :             Value::WalRecord(NeonWalRecord::AuxFile {
      45            0 :                 content: Some(content),
      46              :                 ..
      47            0 :             }) => content.len(),
      48            0 :             Value::WalRecord(NeonWalRecord::Postgres { rec, .. }) => rec.len(),
      49            0 :             Value::WalRecord(NeonWalRecord::ClogSetAborted { xids }) => xids.len() * 4,
      50            0 :             Value::WalRecord(NeonWalRecord::ClogSetCommitted { xids, .. }) => xids.len() * 4,
      51            0 :             Value::WalRecord(NeonWalRecord::MultixactMembersCreate { members, .. }) => {
      52            0 :                 members.len() * 8
      53              :             }
      54          123 :             _ => 8192, /* use image size as the estimation */
      55              :         }
      56            0 :     }
      57              : }
      58              : 
      59              : #[derive(Debug, PartialEq)]
      60              : pub enum InvalidInput {
      61              :     TooShortValue,
      62              :     TooShortPostgresRecord,
      63              : }
      64              : 
      65              : /// We could have a ValueRef where everything is `serde(borrow)`. Before implementing that, lets
      66              : /// use this type for querying if a slice looks some particular way.
      67              : pub struct ValueBytes;
      68              : 
      69              : impl ValueBytes {
      70              :     #[inline(always)]
      71           50 :     pub fn will_init(raw: &[u8]) -> Result<bool, InvalidInput> {
      72           50 :         if raw.len() < 12 {
      73           24 :             return Err(InvalidInput::TooShortValue);
      74           10 :         }
      75              : 
      76           26 :         let value_discriminator = &raw[0..4];
      77              : 
      78           26 :         if value_discriminator == [0, 0, 0, 0] {
      79              :             // Value::Image always initializes
      80            8 :             return Ok(true);
      81            8 :         }
      82              : 
      83           18 :         if value_discriminator != [0, 0, 0, 1] {
      84              :             // not a Value::WalRecord(..)
      85            0 :             return Ok(false);
      86            8 :         }
      87              : 
      88           18 :         let walrecord_discriminator = &raw[4..8];
      89              : 
      90           18 :         if walrecord_discriminator != [0, 0, 0, 0] {
      91              :             // only NeonWalRecord::Postgres can have will_init
      92            1 :             return Ok(false);
      93            7 :         }
      94              : 
      95           17 :         if raw.len() < 17 {
      96            5 :             return Err(InvalidInput::TooShortPostgresRecord);
      97            2 :         }
      98              : 
      99           12 :         Ok(raw[8] == 1)
     100           34 :     }
     101              : }
     102              : 
     103              : #[cfg(test)]
     104              : mod test {
     105              :     use bytes::Bytes;
     106              :     use utils::bin_ser::BeSer;
     107              : 
     108              :     use super::*;
     109              : 
     110              :     macro_rules! roundtrip {
     111              :         ($orig:expr, $expected:expr) => {{
     112              :             let orig: Value = $orig;
     113              : 
     114              :             let actual = Value::ser(&orig).unwrap();
     115              :             let expected: &[u8] = &$expected;
     116              : 
     117              :             assert_eq!(utils::Hex(&actual), utils::Hex(expected));
     118              : 
     119              :             let deser = Value::des(&actual).unwrap();
     120              : 
     121              :             assert_eq!(orig, deser);
     122              :         }};
     123              :     }
     124              : 
     125              :     #[test]
     126            1 :     fn image_roundtrip() {
     127            1 :         let image = Bytes::from_static(b"foobar");
     128            1 :         let image = Value::Image(image);
     129              : 
     130              :         #[rustfmt::skip]
     131            1 :         let expected = [
     132            1 :             // top level discriminator of 4 bytes
     133            1 :             0x00, 0x00, 0x00, 0x00,
     134            1 :             // 8 byte length
     135            1 :             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
     136            1 :             // foobar
     137            1 :             0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72
     138            1 :         ];
     139              : 
     140            1 :         roundtrip!(image, expected);
     141              : 
     142            1 :         assert!(ValueBytes::will_init(&expected).unwrap());
     143            1 :     }
     144              : 
     145              :     #[test]
     146            1 :     fn walrecord_postgres_roundtrip() {
     147            1 :         let rec = NeonWalRecord::Postgres {
     148            1 :             will_init: true,
     149            1 :             rec: Bytes::from_static(b"foobar"),
     150            1 :         };
     151            1 :         let rec = Value::WalRecord(rec);
     152              : 
     153              :         #[rustfmt::skip]
     154            1 :         let expected = [
     155            1 :             // flattened discriminator of total 8 bytes
     156            1 :             0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
     157            1 :             // will_init
     158            1 :             0x01,
     159            1 :             // 8 byte length
     160            1 :             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
     161            1 :             // foobar
     162            1 :             0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72
     163            1 :         ];
     164              : 
     165            1 :         roundtrip!(rec, expected);
     166              : 
     167            1 :         assert!(ValueBytes::will_init(&expected).unwrap());
     168            1 :     }
     169              : 
     170              :     #[test]
     171            1 :     fn bytes_inspection_too_short_image() {
     172            1 :         let rec = Value::Image(Bytes::from_static(b""));
     173              : 
     174              :         #[rustfmt::skip]
     175            1 :         let expected = [
     176            1 :             // top level discriminator of 4 bytes
     177            1 :             0x00, 0x00, 0x00, 0x00,
     178            1 :             // 8 byte length
     179            1 :             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     180            1 :         ];
     181              : 
     182            1 :         roundtrip!(rec, expected);
     183              : 
     184            1 :         assert!(ValueBytes::will_init(&expected).unwrap());
     185            1 :         assert_eq!(expected.len(), 12);
     186           13 :         for len in 0..12 {
     187           12 :             assert_eq!(
     188           12 :                 ValueBytes::will_init(&expected[..len]).unwrap_err(),
     189              :                 InvalidInput::TooShortValue
     190              :             );
     191              :         }
     192            1 :     }
     193              : 
     194              :     #[test]
     195            1 :     fn bytes_inspection_too_short_postgres_record() {
     196            1 :         let rec = NeonWalRecord::Postgres {
     197            1 :             will_init: false,
     198            1 :             rec: Bytes::from_static(b""),
     199            1 :         };
     200            1 :         let rec = Value::WalRecord(rec);
     201              : 
     202              :         #[rustfmt::skip]
     203            1 :         let expected = [
     204            1 :             // flattened discriminator of total 8 bytes
     205            1 :             0x00, 0x00, 0x00, 0x01,
     206            1 :             0x00, 0x00, 0x00, 0x00,
     207            1 :             // will_init
     208            1 :             0x00,
     209            1 :             // 8 byte length
     210            1 :             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     211            1 :         ];
     212              : 
     213            1 :         roundtrip!(rec, expected);
     214              : 
     215            1 :         assert!(!ValueBytes::will_init(&expected).unwrap());
     216            1 :         assert_eq!(expected.len(), 17);
     217            6 :         for len in 12..17 {
     218            5 :             assert_eq!(
     219            5 :                 ValueBytes::will_init(&expected[..len]).unwrap_err(),
     220              :                 InvalidInput::TooShortPostgresRecord
     221              :             )
     222              :         }
     223           13 :         for len in 0..12 {
     224           12 :             assert_eq!(
     225           12 :                 ValueBytes::will_init(&expected[..len]).unwrap_err(),
     226              :                 InvalidInput::TooShortValue
     227              :             )
     228              :         }
     229            1 :     }
     230              : 
     231              :     #[test]
     232            1 :     fn clear_visibility_map_flags_example() {
     233            1 :         let rec = NeonWalRecord::ClearVisibilityMapFlags {
     234            1 :             new_heap_blkno: Some(0x11),
     235            1 :             old_heap_blkno: None,
     236            1 :             flags: 0x03,
     237            1 :         };
     238            1 :         let rec = Value::WalRecord(rec);
     239              : 
     240              :         #[rustfmt::skip]
     241            1 :         let expected = [
     242            1 :             // discriminators
     243            1 :             0x00, 0x00, 0x00, 0x01,
     244            1 :             0x00, 0x00, 0x00, 0x01,
     245            1 :             // Some == 1 followed by 4 bytes
     246            1 :             0x01, 0x00, 0x00, 0x00, 0x11,
     247            1 :             // None == 0
     248            1 :             0x00,
     249            1 :             // flags
     250            1 :             0x03
     251            1 :         ];
     252              : 
     253            1 :         roundtrip!(rec, expected);
     254              : 
     255            1 :         assert!(!ValueBytes::will_init(&expected).unwrap());
     256            1 :     }
     257              : }
        

Generated by: LCOV version 2.1-beta