mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-14 00:42:54 +00:00
Updates storage components to edition 2024. We like to stay on the latest edition if possible. There is no functional changes, however some code changes had to be done to accommodate the edition's breaking changes. The PR has two commits: * the first commit updates storage crates to edition 2024 and appeases `cargo clippy` by changing code. i have accidentially ran the formatter on some files that had other edits. * the second commit performs a `cargo fmt` I would recommend a closer review of the first commit and a less close review of the second one (as it just runs `cargo fmt`). part of https://github.com/neondatabase/neon/issues/10918
240 lines
6.5 KiB
Rust
240 lines
6.5 KiB
Rust
//! This module defines the value type used by the storage engine.
|
|
//!
|
|
//! A [`Value`] represents either a completely new value for one Key ([`Value::Image`]),
|
|
//! or a "delta" of how to get from previous version of the value to the new one
|
|
//! ([`Value::WalRecord`]])
|
|
//!
|
|
//! Note that the [`Value`] type is used for the permananent storage format, so any
|
|
//! changes to it must be backwards compatible.
|
|
|
|
use bytes::Bytes;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::record::NeonWalRecord;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum Value {
|
|
/// An Image value contains a full copy of the value
|
|
Image(Bytes),
|
|
/// A WalRecord value contains a WAL record that needs to be
|
|
/// replayed get the full value. Replaying the WAL record
|
|
/// might need a previous version of the value (if will_init()
|
|
/// returns false), or it may be replayed stand-alone (true).
|
|
WalRecord(NeonWalRecord),
|
|
}
|
|
|
|
impl Value {
|
|
#[inline(always)]
|
|
pub fn is_image(&self) -> bool {
|
|
matches!(self, Value::Image(_))
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn will_init(&self) -> bool {
|
|
match self {
|
|
Value::Image(_) => true,
|
|
Value::WalRecord(rec) => rec.will_init(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum InvalidInput {
|
|
TooShortValue,
|
|
TooShortPostgresRecord,
|
|
}
|
|
|
|
/// We could have a ValueRef where everything is `serde(borrow)`. Before implementing that, lets
|
|
/// use this type for querying if a slice looks some particular way.
|
|
pub struct ValueBytes;
|
|
|
|
impl ValueBytes {
|
|
#[inline(always)]
|
|
pub fn will_init(raw: &[u8]) -> Result<bool, InvalidInput> {
|
|
if raw.len() < 12 {
|
|
return Err(InvalidInput::TooShortValue);
|
|
}
|
|
|
|
let value_discriminator = &raw[0..4];
|
|
|
|
if value_discriminator == [0, 0, 0, 0] {
|
|
// Value::Image always initializes
|
|
return Ok(true);
|
|
}
|
|
|
|
if value_discriminator != [0, 0, 0, 1] {
|
|
// not a Value::WalRecord(..)
|
|
return Ok(false);
|
|
}
|
|
|
|
let walrecord_discriminator = &raw[4..8];
|
|
|
|
if walrecord_discriminator != [0, 0, 0, 0] {
|
|
// only NeonWalRecord::Postgres can have will_init
|
|
return Ok(false);
|
|
}
|
|
|
|
if raw.len() < 17 {
|
|
return Err(InvalidInput::TooShortPostgresRecord);
|
|
}
|
|
|
|
Ok(raw[8] == 1)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use bytes::Bytes;
|
|
use utils::bin_ser::BeSer;
|
|
|
|
use super::*;
|
|
|
|
macro_rules! roundtrip {
|
|
($orig:expr, $expected:expr) => {{
|
|
let orig: Value = $orig;
|
|
|
|
let actual = Value::ser(&orig).unwrap();
|
|
let expected: &[u8] = &$expected;
|
|
|
|
assert_eq!(utils::Hex(&actual), utils::Hex(expected));
|
|
|
|
let deser = Value::des(&actual).unwrap();
|
|
|
|
assert_eq!(orig, deser);
|
|
}};
|
|
}
|
|
|
|
#[test]
|
|
fn image_roundtrip() {
|
|
let image = Bytes::from_static(b"foobar");
|
|
let image = Value::Image(image);
|
|
|
|
#[rustfmt::skip]
|
|
let expected = [
|
|
// top level discriminator of 4 bytes
|
|
0x00, 0x00, 0x00, 0x00,
|
|
// 8 byte length
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
|
|
// foobar
|
|
0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72
|
|
];
|
|
|
|
roundtrip!(image, expected);
|
|
|
|
assert!(ValueBytes::will_init(&expected).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn walrecord_postgres_roundtrip() {
|
|
let rec = NeonWalRecord::Postgres {
|
|
will_init: true,
|
|
rec: Bytes::from_static(b"foobar"),
|
|
};
|
|
let rec = Value::WalRecord(rec);
|
|
|
|
#[rustfmt::skip]
|
|
let expected = [
|
|
// flattened discriminator of total 8 bytes
|
|
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
|
// will_init
|
|
0x01,
|
|
// 8 byte length
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
|
|
// foobar
|
|
0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72
|
|
];
|
|
|
|
roundtrip!(rec, expected);
|
|
|
|
assert!(ValueBytes::will_init(&expected).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn bytes_inspection_too_short_image() {
|
|
let rec = Value::Image(Bytes::from_static(b""));
|
|
|
|
#[rustfmt::skip]
|
|
let expected = [
|
|
// top level discriminator of 4 bytes
|
|
0x00, 0x00, 0x00, 0x00,
|
|
// 8 byte length
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
];
|
|
|
|
roundtrip!(rec, expected);
|
|
|
|
assert!(ValueBytes::will_init(&expected).unwrap());
|
|
assert_eq!(expected.len(), 12);
|
|
for len in 0..12 {
|
|
assert_eq!(
|
|
ValueBytes::will_init(&expected[..len]).unwrap_err(),
|
|
InvalidInput::TooShortValue
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn bytes_inspection_too_short_postgres_record() {
|
|
let rec = NeonWalRecord::Postgres {
|
|
will_init: false,
|
|
rec: Bytes::from_static(b""),
|
|
};
|
|
let rec = Value::WalRecord(rec);
|
|
|
|
#[rustfmt::skip]
|
|
let expected = [
|
|
// flattened discriminator of total 8 bytes
|
|
0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
// will_init
|
|
0x00,
|
|
// 8 byte length
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
];
|
|
|
|
roundtrip!(rec, expected);
|
|
|
|
assert!(!ValueBytes::will_init(&expected).unwrap());
|
|
assert_eq!(expected.len(), 17);
|
|
for len in 12..17 {
|
|
assert_eq!(
|
|
ValueBytes::will_init(&expected[..len]).unwrap_err(),
|
|
InvalidInput::TooShortPostgresRecord
|
|
)
|
|
}
|
|
for len in 0..12 {
|
|
assert_eq!(
|
|
ValueBytes::will_init(&expected[..len]).unwrap_err(),
|
|
InvalidInput::TooShortValue
|
|
)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn clear_visibility_map_flags_example() {
|
|
let rec = NeonWalRecord::ClearVisibilityMapFlags {
|
|
new_heap_blkno: Some(0x11),
|
|
old_heap_blkno: None,
|
|
flags: 0x03,
|
|
};
|
|
let rec = Value::WalRecord(rec);
|
|
|
|
#[rustfmt::skip]
|
|
let expected = [
|
|
// discriminators
|
|
0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x01,
|
|
// Some == 1 followed by 4 bytes
|
|
0x01, 0x00, 0x00, 0x00, 0x11,
|
|
// None == 0
|
|
0x00,
|
|
// flags
|
|
0x03
|
|
];
|
|
|
|
roundtrip!(rec, expected);
|
|
|
|
assert!(!ValueBytes::will_init(&expected).unwrap());
|
|
}
|
|
}
|