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 5497703 : #[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 : }
|