Skip to main content

yo_kv/
value.rs

1//! How a string value sits in a record, and what comes back out of one.
2//!
3//! The map underneath stores opaque bytes against a key. Everything a string
4//! needs beyond those bytes, which is its encoding and its expiry deadline, is
5//! carried in a one byte header in front of them:
6//!
7//! ```text
8//! +--------+-------------------------+-------------------+-----------+
9//! | meta   | expire at, u64 LE       | access, 24 bits   | payload   |
10//! | u8     | 8 bytes, only if tagged | 3 bytes, tagged   | see below |
11//! +--------+-------------------------+-------------------+-----------+
12//! ```
13//!
14//! One byte, and eight more only for a key that has a deadline, which most keys
15//! do not. The alternative is a second lookup into a side table for the TTL, and
16//! a second lookup is a second cache miss on a path whose whole budget is one.
17//!
18//! # The access field
19//!
20//! Three bytes saying when the key was last read, or how often, depending on
21//! which eviction policy is in force. It is [`crate::access::Access`] and the
22//! reasoning about what goes in it lives there.
23//!
24//! It is behind a tag bit the same way the deadline is, but unlike the deadline
25//! every record written now has one. The bit is there so that a record written
26//! before the field existed still reads back correctly rather than to make the
27//! field optional, and it sits after the deadline for the same reason: the
28//! deadline stays at offset one and everything that reads one goes on working.
29//!
30//! Three bytes on every key is a real cost and it is worth being straight about
31//! why it is paid unconditionally rather than only under a policy that reads it.
32//! A key written under `noeviction` and then read under `allkeys-lru` has to be
33//! rankable, and it cannot become rankable later without the record growing,
34//! which means moving it, on what is usually a read. Paying three bytes always
35//! is the version where switching policy at runtime does the obvious thing.
36//!
37//! The payload depends on the encoding. An `int` holds the eight bytes of the
38//! integer and not its digits, which is what makes `INCR` a probe, an add and a
39//! store with no arena traffic at all (`08` section 2). An `embstr` and a `raw`
40//! hold the bytes as given. The difference between those two is the name
41//! `OBJECT ENCODING` reports and nothing else, which is also true in Redis:
42//! `embstr` there means the value was allocated next to its object header, a
43//! distinction `yo` does not have because every value is already next to its
44//! key.
45//!
46//! # The type tag
47//!
48//! The meta byte also says which type the key holds, in three bits that were
49//! spare. That is what `TYPE` reads and it is what a command will read before it
50//! decides whether it is looking at its own type or at somebody else's, and both
51//! of those want the answer to come out of the byte the lookup already fetched
52//! rather than out of a second structure. A string is zero, so nothing written
53//! before the tag existed reads back as anything else.
54//!
55//! # The tier tag
56//!
57//! The last bit says whether the payload is the value or a place on the file
58//! where the value is. That is `14` section 4.1's tier tag, and the reason it is
59//! a bit here rather than a lookup somewhere else is the number it exists to
60//! make possible: at most 1.05 device reads for every point read. A point read
61//! that has to consult a second structure to find out whether the value is
62//! resident has already spent the miss it was trying to avoid.
63//!
64//! A demoted record keeps its deadline, its access field, its type and its
65//! encoding, and adds a length. So `TTL`, `TYPE`, `OBJECT ENCODING`, `STRLEN`,
66//! `EXISTS` and the whole of eviction go on answering at memory speed on a key
67//! whose value is on the device, and only the commands that actually want the
68//! bytes pay for them. [`write_cold_record`] writes one and [`cold`] is the
69//! branch that reads it.
70
71use crate::access::Access;
72use yo_common::Addr;
73use yo_common::num::{parse_i64, push_i64};
74
75/// The longest value Redis calls `embstr` rather than `raw`.
76///
77/// Clients and test suites read `OBJECT ENCODING` and assert on the boundary,
78/// so it is 44 here because it is 44 there (`12` section 2).
79pub const EMBSTR_MAX: usize = 44;
80
81/// What `OBJECT ENCODING` calls a string.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum Encoding {
84    /// The value is an integer, held as an integer.
85    Int,
86    /// A short string, at or under [`EMBSTR_MAX`] bytes.
87    Embstr,
88    /// Everything else.
89    Raw,
90}
91
92impl Encoding {
93    /// The string `OBJECT ENCODING` returns.
94    #[inline]
95    pub const fn name(self) -> &'static str {
96        match self {
97            Encoding::Int => "int",
98            Encoding::Embstr => "embstr",
99            Encoding::Raw => "raw",
100        }
101    }
102
103    /// The encoding Redis would choose for these bytes.
104    ///
105    /// Integer first, because `SET k 42` is int encoded in Redis whatever the
106    /// length, then the `embstr` boundary. The integer test is Redis's own
107    /// `string2ll`, which refuses a leading zero, a leading plus and `-0`, so
108    /// `SET k 007` stays a three byte string and gives back `007`.
109    #[inline]
110    pub fn of(bytes: &[u8]) -> Encoding {
111        if parse_i64(bytes).is_some() {
112            Encoding::Int
113        } else if bytes.len() <= EMBSTR_MAX {
114            Encoding::Embstr
115        } else {
116            Encoding::Raw
117        }
118    }
119}
120
121/// What `TYPE` calls a key, and what the meta byte's tag holds.
122///
123/// The numbers are the same numbers `yo_format::ValueType` uses on disk, so that
124/// saving a key is a copy of the tag rather than a translation of it. There is a
125/// test at the bottom of this file holding the two in step, and it takes a dev
126/// dependency on `yo-format` for no other reason.
127///
128/// The list is shorter than the on disk one because some of those are the same
129/// thing in memory. A bitmap is a string and a HyperLogLog is a string, in Redis
130/// as much as here, and `TYPE` on either answers `string`. The catalog draws
131/// finer lines because a reader wants to know what a blob meant.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum Kind {
134    /// A string, and everything Redis stores as one.
135    String = 0,
136    /// A hash.
137    Hash = 1,
138    /// A set.
139    Set = 2,
140    /// A sorted set.
141    Zset = 3,
142    /// A list.
143    List = 4,
144    /// A stream.
145    Stream = 5,
146    /// A sparse array.
147    Array = 6,
148    /// A body this crate does not know the type of.
149    ///
150    /// The last of the eight patterns the tag can hold, and it is spent on an
151    /// escape rather than on one more type. Everything below this crate is a
152    /// primitive that the document, vector and graph engines are built out of,
153    /// so those engines sit above it and cannot be named here without a cycle,
154    /// and there are three of them and one pattern. An escape holds all three,
155    /// and the body says what it is when asked.
156    ///
157    /// It is the one kind with no `yo_format::catalog::ValueType` beside it,
158    /// because this version's writer does not save a foreign body and there is
159    /// nothing on disk for the catalog to name. When one of them becomes
160    /// saveable it gets its own number there, which does not have to be this
161    /// one, since nothing in a file has to agree with a tag that only ever
162    /// exists in memory.
163    Foreign = 7,
164}
165
166impl Kind {
167    /// The word `TYPE` replies with.
168    ///
169    /// A foreign body knows its own word and this does not, so the answer here
170    /// is the one a caller sees when it has a kind and no body. Go through
171    /// [`crate::Keyspace::type_name`] to get what the client should be told.
172    #[inline]
173    pub const fn name(self) -> &'static str {
174        match self {
175            Kind::String => "string",
176            Kind::Hash => "hash",
177            Kind::Set => "set",
178            Kind::Zset => "zset",
179            Kind::List => "list",
180            Kind::Stream => "stream",
181            Kind::Array => "array",
182            Kind::Foreign => "foreign",
183        }
184    }
185
186    /// Whether the body lives in a slab rather than in the record.
187    ///
188    /// Everything but a string, which is its own record. The arms that walk a
189    /// slot ask this rather than listing six kinds, so a new body kind is one
190    /// arm here and not six lists to find.
191    #[inline]
192    pub const fn is_body(self) -> bool {
193        !matches!(self, Kind::String)
194    }
195
196    /// The kind for a three bit tag.
197    ///
198    /// All eight patterns are spoken for now, and zero is `String`, so this is
199    /// total and no longer has a fallback to explain.
200    #[inline]
201    const fn from_bits(bits: u8) -> Kind {
202        match bits {
203            KIND_HASH => Kind::Hash,
204            KIND_SET => Kind::Set,
205            KIND_ZSET => Kind::Zset,
206            KIND_LIST => Kind::List,
207            KIND_STREAM => Kind::Stream,
208            KIND_ARRAY => Kind::Array,
209            KIND_FOREIGN => Kind::Foreign,
210            _ => Kind::String,
211        }
212    }
213}
214
215/// Bits 0 and 1 of the meta byte: which encoding.
216const ENC_MASK: u8 = 0b0000_0011;
217const ENC_INT: u8 = 0;
218const ENC_EMBSTR: u8 = 1;
219const ENC_RAW: u8 = 2;
220/// Bit 2: whether eight bytes of deadline follow the meta byte.
221const HAS_EXPIRY: u8 = 0b0000_0100;
222/// Bit 6: whether three bytes of access data follow the deadline.
223///
224/// Everything this crate writes now sets it, and the bit exists so that a file
225/// written before it did still opens. A record with the bit clear has no access
226/// field and its payload starts where it always did, which is what makes reading
227/// an older file a matter of asking rather than of knowing which version wrote
228/// it.
229///
230/// It does not work in the other direction. A binary from before this bit reads
231/// a record that sets it, ignores the bit it does not know about, and takes the
232/// three access bytes for the front of the payload. There is no format version
233/// in the file to refuse on, which is worth fixing and is not this change.
234const HAS_ACCESS: u8 = 0b0100_0000;
235/// Bit 7: whether the payload is a place on the file rather than a value.
236///
237/// This is the tier tag `14` section 4.1 needs, and the reason it is a bit in
238/// the meta byte rather than a side table is the whole point of it. A point read
239/// has already paid for the cache line the record starts in by the time it looks
240/// at anything, so asking whether the value is resident is free, and asking any
241/// other way is a second miss on a path whose budget is one. That is what makes
242/// `1.05` device reads per point read a reachable number rather than `2.05`.
243///
244/// A cold record keeps its deadline and its access field in memory. Expiry has
245/// to work on a key whose value is on the device without reading the device, and
246/// so does eviction, or the policy would have to fault in the very keys it is
247/// deciding to get rid of.
248const COLD: u8 = 0b1000_0000;
249/// Bits 3, 4 and 5: which type the key holds.
250///
251/// String is zero, so every record written before the tag existed reads back as
252/// a string, which is what it was.
253const KIND_MASK: u8 = 0b0011_1000;
254const KIND_SHIFT: u32 = 3;
255const KIND_HASH: u8 = 1;
256const KIND_SET: u8 = 2;
257const KIND_ZSET: u8 = 3;
258const KIND_LIST: u8 = 4;
259const KIND_STREAM: u8 = 5;
260const KIND_ARRAY: u8 = 6;
261const KIND_FOREIGN: u8 = 7;
262
263/// Bytes of integer payload, which is a whole `i64` and never its digits.
264const INT_LEN: usize = 8;
265
266/// Bytes of access data, which is [`Access`] and is twenty four bits.
267const ACCESS_LEN: usize = 3;
268
269/// The meta byte in front of every stored string.
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub struct Meta(u8);
272
273impl Meta {
274    /// Build the byte for a type, an encoding and the presence of a deadline.
275    #[inline]
276    pub const fn new(kind: Kind, enc: Encoding, has_expiry: bool) -> Meta {
277        let bits = match enc {
278            Encoding::Int => ENC_INT,
279            Encoding::Embstr => ENC_EMBSTR,
280            Encoding::Raw => ENC_RAW,
281        };
282        Meta(bits | ((kind as u8) << KIND_SHIFT) | if has_expiry { HAS_EXPIRY } else { 0 })
283    }
284
285    /// The byte for a string, which is what everything in `strings.rs` writes.
286    #[inline]
287    pub const fn string(enc: Encoding, has_expiry: bool) -> Meta {
288        Meta::new(Kind::String, enc, has_expiry)
289    }
290
291    /// The byte for a value that lives in a slab, with the record holding a
292    /// number that says where.
293    ///
294    /// The encoding bits are written as zero and mean nothing here. A set's
295    /// encoding is which of the three representations it is in, and that is a
296    /// property of the body and not of the record, so `OBJECT ENCODING` follows
297    /// the number and asks. Keeping a copy of it in these two bits would want
298    /// the record rewritten every time a set was promoted, for a command nobody
299    /// calls in a loop, and two places to disagree about the same fact.
300    #[inline]
301    pub const fn slot(kind: Kind, has_expiry: bool) -> Meta {
302        Meta::new(kind, Encoding::Int, has_expiry)
303    }
304
305    /// Read the byte back.
306    ///
307    /// An unknown encoding is impossible from our own writer, so the two spare
308    /// bit patterns fall to `raw`, which is the reading that returns the bytes
309    /// unchanged rather than reinterpreting them as something else.
310    #[inline]
311    pub const fn from_byte(b: u8) -> Meta {
312        Meta(b)
313    }
314
315    /// The raw byte, as stored.
316    #[inline]
317    pub const fn byte(self) -> u8 {
318        self.0
319    }
320
321    /// Which encoding this value carries.
322    #[inline]
323    pub const fn encoding(self) -> Encoding {
324        match self.0 & ENC_MASK {
325            ENC_INT => Encoding::Int,
326            ENC_EMBSTR => Encoding::Embstr,
327            _ => Encoding::Raw,
328        }
329    }
330
331    /// Which type this key holds.
332    #[inline]
333    pub const fn kind(self) -> Kind {
334        Kind::from_bits((self.0 & KIND_MASK) >> KIND_SHIFT)
335    }
336
337    /// Whether a deadline follows.
338    #[inline]
339    pub const fn has_expiry(self) -> bool {
340        self.0 & HAS_EXPIRY != 0
341    }
342
343    /// Whether an access field follows the deadline.
344    ///
345    /// Everything this crate writes sets it, so in a running server it is always
346    /// true and the reader that checks it is checking something that cannot
347    /// happen. It is here anyway because a bit in the byte costs nothing and the
348    /// alternative was a flag day: without it, the day the field arrived, every
349    /// reader had to agree with every writer at exactly the same moment.
350    ///
351    /// It is not a file format concern. These records live in the arena and
352    /// never reach a file, and the on disk record in `yo_format` has its own
353    /// layout and its own versioning.
354    #[inline]
355    pub const fn has_access(self) -> bool {
356        self.0 & HAS_ACCESS != 0
357    }
358
359    /// The same byte with the access field declared.
360    #[inline]
361    const fn with_access(self) -> Meta {
362        Meta(self.0 | HAS_ACCESS)
363    }
364
365    /// Whether the payload is a place on the file rather than the value.
366    ///
367    /// The one question a point read asks before it decides whether it is going
368    /// to touch a device, and the answer comes out of the byte the lookup has
369    /// already fetched.
370    #[inline]
371    pub const fn is_cold(self) -> bool {
372        self.0 & COLD != 0
373    }
374
375    /// The same byte with the value declared to be on the file.
376    #[inline]
377    pub const fn with_cold(self) -> Meta {
378        Meta(self.0 | COLD)
379    }
380
381    /// Where the access field starts, counting from the meta byte.
382    ///
383    /// After the deadline rather than before it, which is what keeps a record
384    /// written before this field existed readable: the deadline is still at
385    /// offset one and everything that reads one can go on doing so.
386    #[inline]
387    const fn access_at(self) -> usize {
388        if self.has_expiry() { 1 + 8 } else { 1 }
389    }
390
391    /// Where the payload starts, counting from the meta byte.
392    #[inline]
393    pub const fn payload_at(self) -> usize {
394        self.access_at() + if self.has_access() { ACCESS_LEN } else { 0 }
395    }
396}
397
398/// A stored value, read back out of a record.
399///
400/// The point of the two arms is that neither of them copies. An integer comes
401/// back as an integer and is written into the reply buffer as digits at the
402/// moment the reply is built, and a string comes back as a slice of the record
403/// it lives in. Y18 asks for the reply to be constructed once in wire form, and
404/// a `Vec<u8>` in the middle of that is the thing it is asking to remove.
405#[derive(Debug, Clone, Copy, PartialEq, Eq)]
406pub enum Str<'a> {
407    /// An int encoded value.
408    Int(i64),
409    /// Everything else, as it lies in the record.
410    Bytes(&'a [u8]),
411}
412
413impl Str<'_> {
414    /// How many bytes this value is as a string, which is what `STRLEN` returns.
415    #[inline]
416    pub fn len(&self) -> usize {
417        match self {
418            Str::Int(n) => yo_common::num::i64_len(*n),
419            Str::Bytes(b) => b.len(),
420        }
421    }
422
423    /// Whether the value is the empty string.
424    #[inline]
425    pub fn is_empty(&self) -> bool {
426        match self {
427            // No integer writes as no digits.
428            Str::Int(_) => false,
429            Str::Bytes(b) => b.is_empty(),
430        }
431    }
432
433    /// Append the string form to a buffer, which for an integer is its digits.
434    #[inline]
435    pub fn write_to(&self, out: &mut Vec<u8>) {
436        match self {
437            Str::Int(n) => push_i64(out, *n),
438            Str::Bytes(b) => out.extend_from_slice(b),
439        }
440    }
441
442    /// The string form, copied. For the reply path prefer [`Str::write_to`].
443    pub fn to_vec(&self) -> Vec<u8> {
444        let mut v = Vec::with_capacity(self.len());
445        self.write_to(&mut v);
446        v
447    }
448
449    /// The integer this value is, if it is one.
450    ///
451    /// A value can be an integer without being int encoded: `APPEND` and
452    /// `SETRANGE` leave a `raw` string behind, and `INCR` on `"10"` built that
453    /// way is 11 in Redis. So the bytes are parsed rather than the encoding
454    /// being trusted.
455    #[inline]
456    pub fn as_int(&self) -> Option<i64> {
457        match self {
458            Str::Int(n) => Some(*n),
459            Str::Bytes(b) => parse_i64(b),
460        }
461    }
462
463    /// The XXH3 of the value's string form, which is Redis's `DIGEST`.
464    ///
465    /// An int encoded value hashes its digits and not the eight bytes the
466    /// record holds, because the digest a client compares against is the digest
467    /// of what a client would have read.
468    #[must_use]
469    pub fn digest(&self) -> u64 {
470        match self {
471            Str::Bytes(b) => yo_common::xxh3::hash64(b),
472            // Twenty bytes at the most, and the alternative is a second
473            // formatter that writes into a stack buffer for a path nobody calls
474            // in a loop.
475            Str::Int(_) => yo_common::xxh3::hash64(&self.to_vec()),
476        }
477    }
478
479    /// Whether this value's string form is exactly `want`.
480    ///
481    /// `IFEQ` compares against what the client would have read, so an int
482    /// encoded 42 is equal to `"42"` and not to `"042"`. Doing that without
483    /// materialising the digits is why the integer arm exists.
484    #[inline]
485    pub(crate) fn eq_bytes(&self, want: &[u8]) -> bool {
486        match self {
487            Str::Bytes(b) => *b == want,
488            Str::Int(n) => parse_i64(want) == Some(*n),
489        }
490    }
491}
492
493/// How many bytes a record holding this value will occupy.
494///
495/// The access field is counted unconditionally, because every record this crate
496/// writes now carries one. It is not a parameter for that reason: making it one
497/// would put a flag through twenty six call sites to describe something none of
498/// them gets to decide.
499#[inline]
500pub fn record_len(enc: Encoding, payload: usize, has_expiry: bool) -> usize {
501    let head = (if has_expiry { 1 + 8 } else { 1 }) + ACCESS_LEN;
502    head + if enc == Encoding::Int {
503        INT_LEN
504    } else {
505        payload
506    }
507}
508
509/// Write a whole record into `out`, which must be exactly [`record_len`] long.
510///
511/// `bytes` is the string as the caller gave it. When `enc` is [`Encoding::Int`]
512/// the digits are not stored, the integer they parse to is, and the caller has
513/// already established that they parse by choosing that encoding.
514#[inline]
515pub fn write_record(out: &mut [u8], enc: Encoding, bytes: &[u8], expire_at: Option<u64>) {
516    out[0] = Meta::string(enc, expire_at.is_some()).with_access().byte();
517    let mut at = 1;
518    if let Some(ms) = expire_at {
519        out[at..at + 8].copy_from_slice(&ms.to_le_bytes());
520        at += 8;
521    }
522    at += write_blank_access(&mut out[at..]);
523    match enc {
524        Encoding::Int => {
525            let n =
526                parse_i64(bytes).expect("int encoding was chosen for bytes that are not an int");
527            out[at..at + INT_LEN].copy_from_slice(&n.to_le_bytes());
528        }
529        _ => out[at..].copy_from_slice(bytes),
530    }
531}
532
533/// Write a record whose value is an integer the caller already has.
534#[inline]
535pub fn write_int_record(out: &mut [u8], n: i64, expire_at: Option<u64>) {
536    out[0] = Meta::string(Encoding::Int, expire_at.is_some())
537        .with_access()
538        .byte();
539    let mut at = 1;
540    if let Some(ms) = expire_at {
541        out[at..at + 8].copy_from_slice(&ms.to_le_bytes());
542        at += 8;
543    }
544    at += write_blank_access(&mut out[at..]);
545    out[at..at + INT_LEN].copy_from_slice(&n.to_le_bytes());
546}
547
548/// Leave room for the access field and put nothing in it.
549///
550/// The writers here do not know the clock or which policy is in force, and a
551/// record layout is the wrong place to learn either. The keyspace stamps the
552/// field through [`set_access`] once the record is in, which is also where the
553/// decision about whether to stamp at all belongs.
554///
555/// Zero is the most evictable value a key can hold under either reading, which
556/// is the right way round for a default: a key that somehow never got stamped
557/// goes first rather than never.
558#[inline]
559fn write_blank_access(out: &mut [u8]) -> usize {
560    out[..ACCESS_LEN].fill(0);
561    ACCESS_LEN
562}
563
564/// Bytes of slab number, which is how a record points at a body.
565const SLOT_LEN: usize = 4;
566
567/// How many bytes a record pointing at a slab slot occupies.
568#[inline]
569pub fn slot_record_len(has_expiry: bool) -> usize {
570    (if has_expiry { 1 + 8 } else { 1 }) + ACCESS_LEN + SLOT_LEN
571}
572
573/// Write a record that points at `slot` in the slab for `kind`.
574///
575/// `out` must be exactly [`slot_record_len`] long.
576#[inline]
577pub fn write_slot_record(out: &mut [u8], kind: Kind, slot: u32, expire_at: Option<u64>) {
578    out[0] = Meta::slot(kind, expire_at.is_some()).with_access().byte();
579    let mut at = 1;
580    if let Some(ms) = expire_at {
581        out[at..at + 8].copy_from_slice(&ms.to_le_bytes());
582        at += 8;
583    }
584    at += write_blank_access(&mut out[at..]);
585    out[at..at + SLOT_LEN].copy_from_slice(&slot.to_le_bytes());
586}
587
588/// Bytes of file address and value length in a demoted record.
589///
590/// The length is here rather than only on the device because `STRLEN`, `TYPE`,
591/// `OBJECT ENCODING`, `TTL` and `MEMORY USAGE` all have answers that do not need
592/// the bytes, and a tiering layer that faults a value in to answer `STRLEN` is a
593/// tiering layer that has given the game away. Four bytes caps a demoted value
594/// at four gigabytes, which is above the proto-max-bulk-len a server will accept
595/// in the first place.
596const COLD_LEN: usize = 8 + 4;
597
598/// How many bytes a record pointing at a demoted value occupies.
599///
600/// Twelve bytes of payload against however many the value was, which is the
601/// whole reason demotion buys anything. A key with an eight byte value is not
602/// worth demoting and a caller that demotes one will find its memory going up.
603#[inline]
604pub fn cold_record_len(has_expiry: bool) -> usize {
605    (if has_expiry { 1 + 8 } else { 1 }) + ACCESS_LEN + COLD_LEN
606}
607
608/// Write a record saying the value is at `at` on the file and is `len` bytes.
609///
610/// `out` must be exactly [`cold_record_len`] long. `kind` and `enc` are carried
611/// across from the record being replaced so that the questions which can be
612/// answered without the device still can be.
613#[inline]
614pub fn write_cold_record(
615    out: &mut [u8],
616    kind: Kind,
617    enc: Encoding,
618    at: Addr,
619    len: u32,
620    expire_at: Option<u64>,
621) {
622    out[0] = Meta::new(kind, enc, expire_at.is_some())
623        .with_access()
624        .with_cold()
625        .byte();
626    let mut i = 1;
627    if let Some(ms) = expire_at {
628        out[i..i + 8].copy_from_slice(&ms.to_le_bytes());
629        i += 8;
630    }
631    i += write_blank_access(&mut out[i..]);
632    out[i..i + 8].copy_from_slice(&at.to_bits().to_le_bytes());
633    out[i + 8..i + 12].copy_from_slice(&len.to_le_bytes());
634}
635
636/// Where a demoted value is and how big it is.
637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
638pub struct Cold {
639    /// Where on the file the value lives.
640    pub at: Addr,
641    /// How many bytes it is, so the reader can size its buffer in one go and
642    /// the commands that only want the length never go and look.
643    pub len: u32,
644}
645
646/// Where the value of a demoted record is, or `None` if the record is resident.
647///
648/// This is the branch every point read makes. `None` is the common answer and
649/// it costs one test of a bit in a byte that has already been fetched.
650#[inline]
651#[must_use]
652pub fn cold(rec: &[u8]) -> Option<Cold> {
653    let m = Meta::from_byte(rec[0]);
654    if !m.is_cold() {
655        return None;
656    }
657    let i = m.payload_at();
658    let mut a = [0u8; 8];
659    a.copy_from_slice(&rec[i..i + 8]);
660    let mut l = [0u8; 4];
661    l.copy_from_slice(&rec[i + 8..i + 12]);
662    Some(Cold {
663        at: Addr::from_bits(u64::from_le_bytes(a)),
664        len: u32::from_le_bytes(l),
665    })
666}
667
668/// How long the value in a record is, without reading it.
669///
670/// The point of the tier tag seen from the command side: `STRLEN` on a demoted
671/// key is the same cost as `STRLEN` on a resident one. `None` means the record
672/// holds something whose length is not a string length, which is every
673/// collection, and those keep their length in their body.
674#[inline]
675#[must_use]
676pub fn str_len(rec: &[u8]) -> Option<usize> {
677    let m = Meta::from_byte(rec[0]);
678    if m.kind() != Kind::String {
679        return None;
680    }
681    match cold(rec) {
682        Some(c) => Some(c.len as usize),
683        None => Some(read(rec).len()),
684    }
685}
686
687/// What the access field in a record says, or `None` if it has no room for one.
688///
689/// `None` means the record predates the field, which is a key that was written
690/// by an older build and read back out of a file. It is not an error and the
691/// caller should treat it as a key it knows nothing about rather than as a key
692/// that has never been touched.
693#[inline]
694#[must_use]
695pub fn access(rec: &[u8]) -> Option<Access> {
696    let m = Meta::from_byte(rec[0]);
697    if !m.has_access() {
698        return None;
699    }
700    let at = m.access_at();
701    Some(Access::from_bits(u32::from_le_bytes([
702        rec[at],
703        rec[at + 1],
704        rec[at + 2],
705        0,
706    ])))
707}
708
709/// Stamp the access field, in place, over whatever was there.
710///
711/// Returns false for a record with no room, which is the same older record
712/// [`access`] answers `None` for. It is not worth growing one to make room: the
713/// record would have to move, on a path that is usually a read, and the next
714/// write to that key rewrites it with a field anyway.
715#[inline]
716pub fn set_access(rec: &mut [u8], a: Access) -> bool {
717    let m = Meta::from_byte(rec[0]);
718    if !m.has_access() {
719        return false;
720    }
721    let at = m.access_at();
722    rec[at..at + ACCESS_LEN].copy_from_slice(&a.bits().to_le_bytes()[..ACCESS_LEN]);
723    true
724}
725
726/// The slab number in a record that has one.
727///
728/// # Panics
729///
730/// If the record is not one [`write_slot_record`] wrote, which is a caller that
731/// did not read the kind first.
732#[inline]
733pub fn slot(rec: &[u8]) -> u32 {
734    let at = Meta::from_byte(rec[0]).payload_at();
735    let mut b = [0u8; SLOT_LEN];
736    b.copy_from_slice(&rec[at..at + SLOT_LEN]);
737    u32::from_le_bytes(b)
738}
739
740/// The type a record holds.
741#[inline]
742pub fn kind(rec: &[u8]) -> Kind {
743    Meta::from_byte(rec[0]).kind()
744}
745
746/// The deadline in a record, if it has one.
747#[inline]
748pub fn expire_at(rec: &[u8]) -> Option<u64> {
749    let m = Meta::from_byte(rec[0]);
750    if !m.has_expiry() {
751        return None;
752    }
753    let mut b = [0u8; 8];
754    b.copy_from_slice(&rec[1..9]);
755    Some(u64::from_le_bytes(b))
756}
757
758/// Whether a record carries a deadline at all, without reading it.
759///
760/// One byte where [`expire_at`] reads nine, which is the difference between a
761/// question worth asking on every write and one that is not. The count of keys
762/// with deadlines is kept up to date on every record written and every record
763/// deleted, and all it ever needs is this bit.
764#[inline]
765pub fn has_expiry(rec: &[u8]) -> bool {
766    Meta::from_byte(rec[0]).has_expiry()
767}
768
769/// Whether a record's deadline has passed at `now_ms`.
770///
771/// A deadline exactly equal to now has passed, which is Redis's reading: a key
772/// set to expire at time T is gone at time T.
773#[inline]
774pub fn is_expired(rec: &[u8], now_ms: u64) -> bool {
775    match expire_at(rec) {
776        Some(at) => at <= now_ms,
777        None => false,
778    }
779}
780
781/// The value in a record.
782///
783/// # Panics
784///
785/// In a debug build, if the record is demoted. A demoted record's payload is a
786/// file address and reading it as a value would hand back twelve bytes of
787/// address as though they were the string, which is the kind of wrong that
788/// looks like data corruption three layers away. Callers check [`cold`] first,
789/// and that check is the branch they were going to make anyway.
790#[inline]
791pub fn read(rec: &[u8]) -> Str<'_> {
792    let m = Meta::from_byte(rec[0]);
793    debug_assert!(!m.is_cold(), "read on a record whose value is on the file");
794    let at = m.payload_at();
795    match m.encoding() {
796        Encoding::Int => {
797            let mut b = [0u8; INT_LEN];
798            b.copy_from_slice(&rec[at..at + INT_LEN]);
799            Str::Int(i64::from_le_bytes(b))
800        }
801        _ => Str::Bytes(&rec[at..]),
802    }
803}
804
805/// The integer in an int encoded record, and where its bytes start.
806///
807/// Returns `None` for any other encoding. This is the read half of `INCR`'s
808/// fast path, and the offset it hands back is what the write half stores into.
809#[inline]
810pub fn read_int_in_place(rec: &[u8]) -> Option<(i64, usize)> {
811    let m = Meta::from_byte(rec[0]);
812    if m.is_cold() || m.encoding() != Encoding::Int {
813        return None;
814    }
815    let at = m.payload_at();
816    let mut b = [0u8; INT_LEN];
817    b.copy_from_slice(&rec[at..at + INT_LEN]);
818    Some((i64::from_le_bytes(b), at))
819}
820
821/// The bytes of a raw record, to be written over where they lie.
822///
823/// `None` for the other two encodings, and that is not a missing case: a bitmap
824/// write turns an int or an embstr into a raw string, which means moving it, so
825/// there is nothing here for those two to write into. `OBJECT ENCODING` says
826/// `raw` after a `SETBIT` on a value that was `int` a moment before, which is
827/// the same rule seen from the outside.
828#[inline]
829pub fn raw_in_place(rec: &mut [u8]) -> Option<&mut [u8]> {
830    let m = Meta::from_byte(rec[0]);
831    if m.is_cold() || m.encoding() != Encoding::Raw {
832        return None;
833    }
834    let at = m.payload_at();
835    Some(&mut rec[at..])
836}
837
838/// Store `n` back over an int payload that starts at `at`.
839#[inline]
840pub fn write_int_in_place(rec: &mut [u8], at: usize, n: i64) {
841    rec[at..at + INT_LEN].copy_from_slice(&n.to_le_bytes());
842}
843
844#[cfg(test)]
845mod tests {
846    use super::*;
847
848    #[test]
849    fn encoding_follows_redis_boundaries() {
850        assert_eq!(Encoding::of(b"0"), Encoding::Int);
851        assert_eq!(Encoding::of(b"-1"), Encoding::Int);
852        assert_eq!(Encoding::of(b"9223372036854775807"), Encoding::Int);
853        // Past an i64, so it is text and not a number.
854        assert_eq!(Encoding::of(b"9223372036854775808"), Encoding::Embstr);
855        // The three shapes string2ll refuses, all of which must survive as text.
856        assert_eq!(Encoding::of(b"007"), Encoding::Embstr);
857        assert_eq!(Encoding::of(b"+1"), Encoding::Embstr);
858        assert_eq!(Encoding::of(b"-0"), Encoding::Embstr);
859        assert_eq!(Encoding::of(b""), Encoding::Embstr);
860        assert_eq!(Encoding::of(&[b'x'; EMBSTR_MAX]), Encoding::Embstr);
861        assert_eq!(Encoding::of(&[b'x'; EMBSTR_MAX + 1]), Encoding::Raw);
862    }
863
864    const KINDS: [Kind; 8] = [
865        Kind::String,
866        Kind::Hash,
867        Kind::Set,
868        Kind::Zset,
869        Kind::List,
870        Kind::Stream,
871        Kind::Array,
872        Kind::Foreign,
873    ];
874
875    /// The seven that a saved file has a number for, which is every kind but
876    /// the escape.
877    const SAVED: [Kind; 7] = [
878        Kind::String,
879        Kind::Hash,
880        Kind::Set,
881        Kind::Zset,
882        Kind::List,
883        Kind::Stream,
884        Kind::Array,
885    ];
886
887    #[test]
888    fn the_meta_byte_survives_a_round_trip() {
889        for kind in KINDS {
890            for enc in [Encoding::Int, Encoding::Embstr, Encoding::Raw] {
891                for expiry in [false, true] {
892                    let m = Meta::new(kind, enc, expiry);
893                    let back = Meta::from_byte(m.byte());
894                    assert_eq!(back.kind(), kind);
895                    assert_eq!(back.encoding(), enc);
896                    assert_eq!(back.has_expiry(), expiry);
897                    assert_eq!(back.payload_at(), if expiry { 9 } else { 1 });
898                }
899            }
900        }
901    }
902
903    #[test]
904    fn the_three_fields_of_the_meta_byte_do_not_reach_into_each_other() {
905        // Forty two combinations, all of which have to come out of one byte
906        // with nothing borrowed from a neighbour. A tag that overlapped the
907        // expiry bit would read the payload at the wrong offset, which is a
908        // corrupt value rather than a wrong answer.
909        let mut seen = std::collections::HashSet::new();
910        for kind in KINDS {
911            for enc in [Encoding::Int, Encoding::Embstr, Encoding::Raw] {
912                for expiry in [false, true] {
913                    assert!(
914                        seen.insert(Meta::new(kind, enc, expiry).byte()),
915                        "{kind:?} {enc:?} {expiry} collides with something else"
916                    );
917                }
918            }
919        }
920        assert_eq!(seen.len(), KINDS.len() * 6);
921    }
922
923    #[test]
924    fn a_record_written_before_the_tag_existed_is_a_string() {
925        // Bits 3 to 7 were zero in every record M2 wrote, and zero is String.
926        // This is the whole reason String is zero, so it is worth a test that
927        // fails if somebody renumbers the enum alphabetically one day.
928        assert_eq!(Kind::String as u8, 0);
929        assert_eq!(Meta::from_byte(0b0000_0101).kind(), Kind::String);
930        assert_eq!(Meta::from_byte(0b0000_0101).encoding(), Encoding::Embstr);
931        assert!(Meta::from_byte(0b0000_0101).has_expiry());
932    }
933
934    #[test]
935    fn the_tag_is_the_number_the_file_format_uses() {
936        use yo_format::catalog::ValueType;
937        // Not a translation table, an assertion that no translation is needed.
938        // If these ever diverge, saving a key has to map between them, and the
939        // mapping is the kind of thing that gets one arm wrong.
940        assert_eq!(Kind::String as u8, ValueType::String as u8);
941        assert_eq!(Kind::Hash as u8, ValueType::Hash as u8);
942        assert_eq!(Kind::Set as u8, ValueType::Set as u8);
943        assert_eq!(Kind::Zset as u8, ValueType::Zset as u8);
944        assert_eq!(Kind::List as u8, ValueType::List as u8);
945        assert_eq!(Kind::Stream as u8, ValueType::Stream as u8);
946        assert_eq!(Kind::Array as u8, ValueType::Array as u8);
947        // `Kind::Foreign` is deliberately not in this list. It is the escape
948        // for a body that lives above this crate, nothing saves one yet, and so
949        // there is no number on disk for it to have to match. Its tag is 7 and
950        // the catalog's 7 is a bitmap, which is a string in memory and never
951        // becomes a `Kind`, so the two cannot meet.
952        // And the words agree, because both of them end up on a wire.
953        for k in SAVED {
954            let v = ValueType::from_u8(k as u8).expect("the catalog knows this one");
955            assert_eq!(k.name(), v.redis_name(), "{k:?}");
956        }
957    }
958
959    #[test]
960    fn a_string_record_is_tagged_as_one() {
961        for text in [&b"42"[..], b"hello", &[b'z'; 100]] {
962            for expire in [None, Some(9_000u64)] {
963                assert_eq!(kind(&record(text, expire)), Kind::String);
964            }
965        }
966        let mut v = vec![0u8; record_len(Encoding::Int, 0, false)];
967        write_int_record(&mut v, 7, None);
968        assert_eq!(kind(&v), Kind::String);
969    }
970
971    fn record(bytes: &[u8], expire: Option<u64>) -> Vec<u8> {
972        let enc = Encoding::of(bytes);
973        let mut v = vec![0u8; record_len(enc, bytes.len(), expire.is_some())];
974        write_record(&mut v, enc, bytes, expire);
975        v
976    }
977
978    #[test]
979    fn a_record_gives_back_what_went_into_it() {
980        for text in [
981            &b""[..],
982            b"x",
983            b"0",
984            b"-1",
985            b"42",
986            b"007",
987            b"-0",
988            b"hello world",
989            &[b'z'; 100],
990        ] {
991            for expire in [None, Some(1_234_567_890_123u64)] {
992                let r = record(text, expire);
993                assert_eq!(read(&r).to_vec(), text, "{text:?} at {expire:?}");
994                assert_eq!(read(&r).len(), text.len(), "{text:?} length");
995                assert_eq!(expire_at(&r), expire, "{text:?} deadline");
996            }
997        }
998    }
999
1000    #[test]
1001    fn an_integer_costs_the_same_however_many_digits_it_has() {
1002        let small = record(b"1", None);
1003        let large = record(b"-9223372036854775808", None);
1004        assert_eq!(small.len(), large.len());
1005        assert_eq!(read(&large), Str::Int(i64::MIN));
1006        assert_eq!(read(&large).to_vec(), b"-9223372036854775808");
1007    }
1008
1009    #[test]
1010    fn an_integer_is_incremented_where_it_lies() {
1011        let mut r = record(b"41", Some(99));
1012        let (n, at) = read_int_in_place(&r).expect("int encoded");
1013        assert_eq!(n, 41);
1014        write_int_in_place(&mut r, at, n + 1);
1015        assert_eq!(read(&r), Str::Int(42));
1016        // The deadline was in front of the payload and is still there.
1017        assert_eq!(expire_at(&r), Some(99));
1018    }
1019
1020    #[test]
1021    fn a_string_is_not_read_as_an_integer_in_place() {
1022        let r = record(b"hello", None);
1023        assert!(read_int_in_place(&r).is_none());
1024    }
1025
1026    #[test]
1027    fn a_deadline_that_is_now_has_passed() {
1028        let r = record(b"v", Some(100));
1029        assert!(!is_expired(&r, 99));
1030        assert!(is_expired(&r, 100));
1031        assert!(is_expired(&r, 101));
1032        let forever = record(b"v", None);
1033        assert!(!is_expired(&forever, u64::MAX));
1034    }
1035
1036    #[test]
1037    fn a_value_that_is_text_can_still_be_a_number() {
1038        // What `APPEND` leaves behind, and what `INCR` has to accept.
1039        assert_eq!(Str::Bytes(b"10").as_int(), Some(10));
1040        assert_eq!(Str::Bytes(b"10x").as_int(), None);
1041        assert_eq!(Str::Int(-5).as_int(), Some(-5));
1042    }
1043
1044    #[test]
1045    fn an_unknown_encoding_reads_as_raw_bytes() {
1046        // Nothing we write produces bit pattern three, but a record that has
1047        // been through a future writer might, and guessing `int` on it would
1048        // reinterpret eight bytes of somebody's string as a number.
1049        let m = Meta::from_byte(0b11);
1050        assert_eq!(m.encoding(), Encoding::Raw);
1051    }
1052
1053    #[test]
1054    fn every_tag_pattern_is_spoken_for() {
1055        // All eight of them now, the last one being the escape a body this
1056        // crate cannot name is held under. There is no pattern left over to
1057        // read as a string, so the day a ninth kind is wanted the tag has to
1058        // grow a bit rather than borrow one.
1059        assert_eq!(Meta::from_byte(6 << 3).kind(), Kind::Array);
1060        assert_eq!(Meta::from_byte(7 << 3).kind(), Kind::Foreign);
1061    }
1062
1063    #[test]
1064    fn the_bits_above_the_tag_do_not_disturb_it() {
1065        // Bit 6 is now the access flag and bit 7 is still free, and neither of
1066        // them may move the tag, so this is the check that the tag is three
1067        // bits and not five.
1068        assert_eq!(Meta::from_byte(0b1100_0000).kind(), Kind::String);
1069        assert_eq!(Meta::from_byte(0b1101_0000).kind(), Kind::Set);
1070        // And the flag itself reads off the byte rather than off the tag.
1071        assert!(Meta::from_byte(0b0100_0000).has_access());
1072        assert!(!Meta::from_byte(0b1011_1111).has_access());
1073    }
1074
1075    /// Every record this crate writes has room for an access field, and the
1076    /// field starts empty.
1077    #[test]
1078    fn a_fresh_record_has_an_unstamped_access_field() {
1079        for expire in [None, Some(1_700_000_000_000)] {
1080            for (enc, bytes) in [
1081                (Encoding::Int, &b"42"[..]),
1082                (Encoding::Embstr, b"hello"),
1083                (Encoding::Raw, &[b'x'; 64][..]),
1084            ] {
1085                let mut rec = vec![0u8; record_len(enc, bytes.len(), expire.is_some())];
1086                write_record(&mut rec, enc, bytes, expire);
1087                let a = access(&rec).expect("a record we just wrote has the field");
1088                assert!(a.is_unset(), "{enc:?} came out stamped");
1089                assert_eq!(expire_at(&rec), expire, "{enc:?} lost its deadline");
1090                match enc {
1091                    Encoding::Int => assert_eq!(read(&rec), Str::Int(42)),
1092                    _ => assert_eq!(read(&rec), Str::Bytes(bytes)),
1093                }
1094            }
1095        }
1096    }
1097
1098    /// The same for the other two writers, which is every record shape there is.
1099    #[test]
1100    fn slot_and_int_records_have_the_field_too() {
1101        for expire in [None, Some(9_000)] {
1102            let mut rec = vec![0u8; slot_record_len(expire.is_some())];
1103            write_slot_record(&mut rec, Kind::Set, 77, expire);
1104            assert!(access(&rec).expect("the field").is_unset());
1105            assert_eq!(slot(&rec), 77);
1106            assert_eq!(kind(&rec), Kind::Set);
1107            assert_eq!(expire_at(&rec), expire);
1108
1109            let mut rec = vec![0u8; record_len(Encoding::Int, 0, expire.is_some())];
1110            write_int_record(&mut rec, -5, expire);
1111            assert!(access(&rec).expect("the field").is_unset());
1112            assert_eq!(read(&rec), Str::Int(-5));
1113            assert_eq!(expire_at(&rec), expire);
1114        }
1115    }
1116
1117    /// Stamping the field does not disturb anything either side of it.
1118    ///
1119    /// It sits between the deadline and the payload and it is written in place
1120    /// on a path that is usually a read, so an off by one here would corrupt a
1121    /// value quietly rather than fail.
1122    #[test]
1123    fn stamping_the_field_leaves_the_deadline_and_the_payload_alone() {
1124        let deadline = 1_700_000_000_123u64;
1125        let body = b"the payload nobody should touch";
1126        let mut rec = vec![0u8; record_len(Encoding::Raw, body.len(), true)];
1127        write_record(&mut rec, Encoding::Raw, body, Some(deadline));
1128
1129        for bits in [1u32, 0xff, 0x00ff_ffff, 0x0012_3456] {
1130            let a = Access::from_bits(bits);
1131            assert!(set_access(&mut rec, a));
1132            assert_eq!(access(&rec), Some(a), "{bits:#x} did not survive");
1133            assert_eq!(
1134                expire_at(&rec),
1135                Some(deadline),
1136                "{bits:#x} hit the deadline"
1137            );
1138            assert_eq!(read(&rec), Str::Bytes(body), "{bits:#x} hit the payload");
1139        }
1140    }
1141
1142    /// A record written before the field existed still reads correctly, and
1143    /// refuses to be stamped rather than being stamped over its payload.
1144    ///
1145    /// This is the whole reason the field is behind a tag bit instead of just
1146    /// always being there. A file written by an older build has records with the
1147    /// bit clear, and their payload starts three bytes earlier.
1148    #[test]
1149    fn a_record_from_before_the_field_still_reads() {
1150        // Built by hand, the way the old writer did it: meta, deadline, payload,
1151        // and no access field.
1152        let body = b"older";
1153        let mut old = vec![Meta::string(Encoding::Raw, true).byte()];
1154        old.extend_from_slice(&7_000u64.to_le_bytes());
1155        old.extend_from_slice(body);
1156
1157        assert!(!Meta::from_byte(old[0]).has_access());
1158        assert_eq!(access(&old), None, "there is no field to read");
1159        assert_eq!(expire_at(&old), Some(7_000));
1160        assert_eq!(read(&old), Str::Bytes(body));
1161
1162        let before = old.clone();
1163        assert!(
1164            !set_access(&mut old, Access::from_bits(0xabcdef)),
1165            "it should refuse rather than write over the payload"
1166        );
1167        assert_eq!(old, before, "it wrote something anyway");
1168    }
1169
1170    /// The field costs three bytes on every record and no more.
1171    #[test]
1172    fn the_field_costs_three_bytes() {
1173        assert_eq!(record_len(Encoding::Raw, 10, false), 1 + 3 + 10);
1174        assert_eq!(record_len(Encoding::Raw, 10, true), 1 + 8 + 3 + 10);
1175        assert_eq!(record_len(Encoding::Int, 10, false), 1 + 3 + 8);
1176        assert_eq!(slot_record_len(false), 1 + 3 + 4);
1177        assert_eq!(slot_record_len(true), 1 + 8 + 3 + 4);
1178    }
1179
1180    /// A place on the file to demote a value to.
1181    fn somewhere() -> Addr {
1182        Addr::new(yo_common::Space::Log, 4096)
1183    }
1184
1185    #[test]
1186    fn a_demoted_record_says_so_in_the_byte_the_lookup_already_read() {
1187        let mut rec = vec![0u8; cold_record_len(false)];
1188        write_cold_record(
1189            &mut rec,
1190            Kind::String,
1191            Encoding::Raw,
1192            somewhere(),
1193            900,
1194            None,
1195        );
1196        // The one branch a point read makes, and it is a bit in rec[0].
1197        let c = cold(&rec).expect("the record is demoted");
1198        assert_eq!(c.at, somewhere());
1199        assert_eq!(c.len, 900);
1200    }
1201
1202    #[test]
1203    fn a_resident_record_is_not_demoted() {
1204        let mut rec = vec![0u8; record_len(Encoding::Raw, 3, false)];
1205        write_record(&mut rec, Encoding::Raw, b"abc", None);
1206        assert_eq!(cold(&rec), None);
1207    }
1208
1209    #[test]
1210    fn a_demoted_record_answers_everything_that_is_not_the_bytes() {
1211        let deadline = 1_700_000_000_000u64;
1212        let mut rec = vec![0u8; cold_record_len(true)];
1213        write_cold_record(
1214            &mut rec,
1215            Kind::String,
1216            Encoding::Raw,
1217            somewhere(),
1218            77,
1219            Some(deadline),
1220        );
1221        // None of these is allowed to want the device.
1222        assert_eq!(kind(&rec), Kind::String);
1223        assert_eq!(expire_at(&rec), Some(deadline));
1224        assert_eq!(str_len(&rec), Some(77));
1225        assert_eq!(Meta::from_byte(rec[0]).encoding(), Encoding::Raw);
1226        assert!(access(&rec).is_some());
1227    }
1228
1229    #[test]
1230    fn a_demoted_record_can_still_be_stamped_and_ranked() {
1231        // Eviction has to be able to score a key whose value is on the file,
1232        // or the policy would fault in the keys it is trying to get rid of.
1233        let mut rec = vec![0u8; cold_record_len(false)];
1234        write_cold_record(
1235            &mut rec,
1236            Kind::String,
1237            Encoding::Embstr,
1238            somewhere(),
1239            5,
1240            None,
1241        );
1242        let a = Access::from_bits(12345);
1243        assert!(set_access(&mut rec, a));
1244        assert_eq!(access(&rec), Some(a));
1245        // And the address survived being written around.
1246        assert_eq!(cold(&rec).map(|c| c.at), Some(somewhere()));
1247    }
1248
1249    #[test]
1250    fn the_in_place_writers_refuse_a_demoted_record() {
1251        let mut rec = vec![0u8; cold_record_len(false)];
1252        write_cold_record(&mut rec, Kind::String, Encoding::Int, somewhere(), 2, None);
1253        // The payload of this record parses as an int if you do not look at the
1254        // tier tag first, which is exactly the bug the check is here to stop.
1255        assert_eq!(read_int_in_place(&rec), None);
1256        assert_eq!(raw_in_place(&mut rec), None);
1257    }
1258
1259    #[test]
1260    fn demoting_a_long_value_is_what_buys_the_memory() {
1261        // Twelve bytes of payload whatever the value was. A short value is not
1262        // worth demoting and the arithmetic says so rather than a comment.
1263        let long = record_len(Encoding::Raw, 4096, false);
1264        assert!(
1265            cold_record_len(false) < long / 100,
1266            "{} against {long}",
1267            cold_record_len(false)
1268        );
1269        assert!(cold_record_len(false) > record_len(Encoding::Raw, 8, false));
1270    }
1271
1272    #[test]
1273    fn str_len_on_a_resident_string_is_the_value_length() {
1274        let mut rec = vec![0u8; record_len(Encoding::Raw, 3, false)];
1275        write_record(&mut rec, Encoding::Raw, b"abc", None);
1276        assert_eq!(str_len(&rec), Some(3));
1277
1278        let mut n = vec![0u8; record_len(Encoding::Int, 0, false)];
1279        write_int_record(&mut n, -1234, None);
1280        assert_eq!(str_len(&n), Some(5));
1281    }
1282}