LCOV - code coverage report
Current view: top level - libs/pageserver_api/src - value.rs (source / functions) Coverage Total Hit
Test: 5fe7fa8d483b39476409aee736d6d5e32728bfac.info Lines: 99.3 % 149 148
Test Date: 2025-03-12 16:10:49 Functions: 64.3 % 14 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::record::NeonWalRecord;
      14              : 
      15      5497276 : #[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         1576 :     pub fn is_image(&self) -> bool {
      29         1576 :         matches!(self, Value::Image(_))
      30              :     }
      31              : 
      32              :     #[inline(always)]
      33     14360093 :     pub fn will_init(&self) -> bool {
      34     14360093 :         match self {
      35     14066898 :             Value::Image(_) => true,
      36       293195 :             Value::WalRecord(rec) => rec.will_init(),
      37              :         }
      38              :     }
      39              : }
      40              : 
      41              : #[derive(Debug, PartialEq)]
      42              : pub enum InvalidInput {
      43              :     TooShortValue,
      44              :     TooShortPostgresRecord,
      45              : }
      46              : 
      47              : /// We could have a ValueRef where everything is `serde(borrow)`. Before implementing that, lets
      48              : /// use this type for querying if a slice looks some particular way.
      49              : pub struct ValueBytes;
      50              : 
      51              : impl ValueBytes {
      52              :     #[inline(always)]
      53           98 :     pub fn will_init(raw: &[u8]) -> Result<bool, InvalidInput> {
      54           98 :         if raw.len() < 12 {
      55           24 :             return Err(InvalidInput::TooShortValue);
      56           74 :         }
      57           74 : 
      58           74 :         let value_discriminator = &raw[0..4];
      59           74 : 
      60           74 :         if value_discriminator == [0, 0, 0, 0] {
      61              :             // Value::Image always initializes
      62           26 :             return Ok(true);
      63           48 :         }
      64           48 : 
      65           48 :         if value_discriminator != [0, 0, 0, 1] {
      66              :             // not a Value::WalRecord(..)
      67            0 :             return Ok(false);
      68           48 :         }
      69           48 : 
      70           48 :         let walrecord_discriminator = &raw[4..8];
      71           48 : 
      72           48 :         if walrecord_discriminator != [0, 0, 0, 0] {
      73              :             // only NeonWalRecord::Postgres can have will_init
      74            1 :             return Ok(false);
      75           47 :         }
      76           47 : 
      77           47 :         if raw.len() < 17 {
      78            5 :             return Err(InvalidInput::TooShortPostgresRecord);
      79           42 :         }
      80           42 : 
      81           42 :         Ok(raw[8] == 1)
      82           34 :     }
      83              : }
      84              : 
      85              : #[cfg(test)]
      86              : mod test {
      87              :     use bytes::Bytes;
      88              :     use utils::bin_ser::BeSer;
      89              : 
      90              :     use super::*;
      91              : 
      92              :     macro_rules! roundtrip {
      93              :         ($orig:expr, $expected:expr) => {{
      94              :             let orig: Value = $orig;
      95              : 
      96              :             let actual = Value::ser(&orig).unwrap();
      97              :             let expected: &[u8] = &$expected;
      98              : 
      99              :             assert_eq!(utils::Hex(&actual), utils::Hex(expected));
     100              : 
     101              :             let deser = Value::des(&actual).unwrap();
     102              : 
     103              :             assert_eq!(orig, deser);
     104              :         }};
     105              :     }
     106              : 
     107              :     #[test]
     108            1 :     fn image_roundtrip() {
     109            1 :         let image = Bytes::from_static(b"foobar");
     110            1 :         let image = Value::Image(image);
     111            1 : 
     112            1 :         #[rustfmt::skip]
     113            1 :         let expected = [
     114            1 :             // top level discriminator of 4 bytes
     115            1 :             0x00, 0x00, 0x00, 0x00,
     116            1 :             // 8 byte length
     117            1 :             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
     118            1 :             // foobar
     119            1 :             0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72
     120            1 :         ];
     121            1 : 
     122            1 :         roundtrip!(image, expected);
     123              : 
     124            1 :         assert!(ValueBytes::will_init(&expected).unwrap());
     125            1 :     }
     126              : 
     127              :     #[test]
     128            1 :     fn walrecord_postgres_roundtrip() {
     129            1 :         let rec = NeonWalRecord::Postgres {
     130            1 :             will_init: true,
     131            1 :             rec: Bytes::from_static(b"foobar"),
     132            1 :         };
     133            1 :         let rec = Value::WalRecord(rec);
     134            1 : 
     135            1 :         #[rustfmt::skip]
     136            1 :         let expected = [
     137            1 :             // flattened discriminator of total 8 bytes
     138            1 :             0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
     139            1 :             // will_init
     140            1 :             0x01,
     141            1 :             // 8 byte length
     142            1 :             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
     143            1 :             // foobar
     144            1 :             0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72
     145            1 :         ];
     146            1 : 
     147            1 :         roundtrip!(rec, expected);
     148              : 
     149            1 :         assert!(ValueBytes::will_init(&expected).unwrap());
     150            1 :     }
     151              : 
     152              :     #[test]
     153            1 :     fn bytes_inspection_too_short_image() {
     154            1 :         let rec = Value::Image(Bytes::from_static(b""));
     155            1 : 
     156            1 :         #[rustfmt::skip]
     157            1 :         let expected = [
     158            1 :             // top level discriminator of 4 bytes
     159            1 :             0x00, 0x00, 0x00, 0x00,
     160            1 :             // 8 byte length
     161            1 :             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     162            1 :         ];
     163            1 : 
     164            1 :         roundtrip!(rec, expected);
     165              : 
     166            1 :         assert!(ValueBytes::will_init(&expected).unwrap());
     167            1 :         assert_eq!(expected.len(), 12);
     168           13 :         for len in 0..12 {
     169           12 :             assert_eq!(
     170           12 :                 ValueBytes::will_init(&expected[..len]).unwrap_err(),
     171           12 :                 InvalidInput::TooShortValue
     172           12 :             );
     173              :         }
     174            1 :     }
     175              : 
     176              :     #[test]
     177            1 :     fn bytes_inspection_too_short_postgres_record() {
     178            1 :         let rec = NeonWalRecord::Postgres {
     179            1 :             will_init: false,
     180            1 :             rec: Bytes::from_static(b""),
     181            1 :         };
     182            1 :         let rec = Value::WalRecord(rec);
     183            1 : 
     184            1 :         #[rustfmt::skip]
     185            1 :         let expected = [
     186            1 :             // flattened discriminator of total 8 bytes
     187            1 :             0x00, 0x00, 0x00, 0x01,
     188            1 :             0x00, 0x00, 0x00, 0x00,
     189            1 :             // will_init
     190            1 :             0x00,
     191            1 :             // 8 byte length
     192            1 :             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     193            1 :         ];
     194            1 : 
     195            1 :         roundtrip!(rec, expected);
     196              : 
     197            1 :         assert!(!ValueBytes::will_init(&expected).unwrap());
     198            1 :         assert_eq!(expected.len(), 17);
     199            6 :         for len in 12..17 {
     200            5 :             assert_eq!(
     201            5 :                 ValueBytes::will_init(&expected[..len]).unwrap_err(),
     202            5 :                 InvalidInput::TooShortPostgresRecord
     203            5 :             )
     204              :         }
     205           13 :         for len in 0..12 {
     206           12 :             assert_eq!(
     207           12 :                 ValueBytes::will_init(&expected[..len]).unwrap_err(),
     208           12 :                 InvalidInput::TooShortValue
     209           12 :             )
     210              :         }
     211            1 :     }
     212              : 
     213              :     #[test]
     214            1 :     fn clear_visibility_map_flags_example() {
     215            1 :         let rec = NeonWalRecord::ClearVisibilityMapFlags {
     216            1 :             new_heap_blkno: Some(0x11),
     217            1 :             old_heap_blkno: None,
     218            1 :             flags: 0x03,
     219            1 :         };
     220            1 :         let rec = Value::WalRecord(rec);
     221            1 : 
     222            1 :         #[rustfmt::skip]
     223            1 :         let expected = [
     224            1 :             // discriminators
     225            1 :             0x00, 0x00, 0x00, 0x01,
     226            1 :             0x00, 0x00, 0x00, 0x01,
     227            1 :             // Some == 1 followed by 4 bytes
     228            1 :             0x01, 0x00, 0x00, 0x00, 0x11,
     229            1 :             // None == 0
     230            1 :             0x00,
     231            1 :             // flags
     232            1 :             0x03
     233            1 :         ];
     234            1 : 
     235            1 :         roundtrip!(rec, expected);
     236              : 
     237            1 :         assert!(!ValueBytes::will_init(&expected).unwrap());
     238            1 :     }
     239              : }
        

Generated by: LCOV version 2.1-beta