yo/keys.rs
1//! The keyspace itself: what is there, what type it is, and when it goes away.
2//!
3//! [`Strings`](crate::Strings) and [`Sets`](crate::Sets) each hold the commands
4//! for one type. The commands here hold for all of them, because a deadline is
5//! not a string thing or a set thing. `EXPIRE` puts the moment in the key's own
6//! record, so the same call works on a key holding a string, a set or a hash and
7//! costs the same on each.
8//!
9//! ```
10//! use std::time::Duration;
11//!
12//! let db = yo::open(yo::MEMORY)?;
13//! let keys = db.keys();
14//!
15//! db.set("online").add("alice")?;
16//! keys.expire_in("online", Duration::from_secs(60))?;
17//!
18//! assert_eq!(keys.kind("online")?, Some(yo::Kind::Set));
19//! assert!(keys.ttl("online")?.left().is_some());
20//! # Ok::<(), yo::Error>(())
21//! ```
22//!
23//! # What a deadline is not
24//!
25//! It is not a property of the value. Giving a set a deadline rewrites five
26//! bytes of the key's record and does not touch a single member, which is why
27//! [`Keys::expire_in`] on a set of a million members is the same call as on a
28//! set of one.
29//!
30//! It is also not the same as the per field deadlines a hash can carry. Those
31//! are `HEXPIRE` and they live in the hash. A key can have a deadline while its
32//! fields have their own, and neither one knows about the other.
33//!
34//! # The clock is only read when it matters
35//!
36//! A database that has never been asked for a deadline never reads the clock on
37//! the data path, which [`Db::reads_the_clock`](crate::Db::reads_the_clock)
38//! reports. The first call here that creates one turns that on for good, so it
39//! is worth knowing that this is where the tens of nanoseconds come from.
40//!
41//! # There is no touch
42//!
43//! Redis has a `TOUCH`, and on a real server it counts the keys that are there
44//! and moves each of them up the eviction order. There is no eviction here, so
45//! all it could do is count, and [`Keys::count`] already does that. A second
46//! name for one call is worse than no second name, so the wire has `TOUCH` for
47//! the clients that send it and this does not.
48//!
49//! # There is no cursor
50//!
51//! The wire has `SCAN` because a server cannot stop and walk a keyspace for one
52//! client while every other client waits, so it hands out a number and does the
53//! walk in pieces. Nothing here is in that position. [`Keys::each`] holds the
54//! database for as long as it runs and nothing else can write to it in the
55//! meantime, so it is one walk, it sees one version of the keyspace, and there
56//! is no cursor to hold and no duplicate to filter out.
57//!
58//! What that costs is that a walk of ten million keys is ten million calls
59//! before the next line of your program runs. That is the same trade `KEYS`
60//! makes and it is the right one here, because the thing on the other side of
61//! the call is your own code rather than a socket.
62//!
63//! # The typed collections are somewhere else
64//!
65//! A [`Map`](crate::Map) is a named collection and not a key in the keyspace, so
66//! it does not show up here and cannot be given a deadline. That is `15`
67//! section 3's split and not an oversight: a map's name is checked when it is
68//! opened, and a key's name is whatever you pass.
69
70use std::time::{Duration, SystemTime, UNIX_EPOCH};
71
72use yo_common::{Code, Error, Result};
73use yo_kv::{Applied, Ask, Cond, Kind, MAX_AT, Moved};
74
75use crate::db::Handle;
76
77/// What a key says about when it goes away.
78///
79/// Three answers rather than two, because a key that is not there and a key
80/// that is never going away are different things and code that confuses them
81/// deletes the wrong data. Redis says this with `-2` and `-1` and hopes you
82/// read the manual.
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum Ttl {
85 /// There is no such key.
86 Missing,
87 /// The key is there and nothing is going to take it away.
88 Forever,
89 /// The key is there and this much of it is left.
90 In(Duration),
91}
92
93impl Ttl {
94 /// How long is left, or `None` for a key that is missing or has no
95 /// deadline.
96 ///
97 /// The short answer for code that only wants to know whether it should
98 /// refresh something. When the difference matters, match on the variants.
99 #[must_use]
100 pub fn left(self) -> Option<Duration> {
101 match self {
102 Ttl::In(left) => Some(left),
103 _ => None,
104 }
105 }
106
107 /// Whether the key is there at all.
108 #[must_use]
109 pub fn found(self) -> bool {
110 !matches!(self, Ttl::Missing)
111 }
112}
113
114/// Whether a deadline is allowed to move, which is `EXPIRE`'s `NX`, `XX`, `GT`
115/// and `LT`.
116///
117/// The condition is checked before the moment is, so a deadline that has
118/// already gone and a condition that says no leaves the key alone rather than
119/// deleting it.
120#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
121pub enum When {
122 /// Whatever is there now. Plain `EXPIRE`.
123 #[default]
124 Always,
125 /// Only if the key has no deadline yet. `NX`.
126 Unset,
127 /// Only if it already has one. `XX`.
128 AlreadySet,
129 /// Only if this pushes the deadline further out. `GT`.
130 ///
131 /// A key with no deadline is refused, because no deadline reads as
132 /// infinitely far away and nothing is further out than that.
133 Later,
134 /// Only if this brings the deadline in. `LT`.
135 ///
136 /// A key with no deadline is accepted, by the same reading.
137 Earlier,
138 /// Only if there is one now and this brings it in. `XX LT`.
139 ///
140 /// The one combination the other five cannot say. `XX GT` is just `GT`,
141 /// since `GT` already refuses a key with no deadline.
142 EarlierAndAlreadySet,
143}
144
145impl From<When> for Cond {
146 fn from(when: When) -> Cond {
147 match when {
148 When::Always => Cond::Always,
149 When::Unset => Cond::NotSet,
150 When::AlreadySet => Cond::AlreadySet,
151 When::Later => Cond::Greater,
152 When::Earlier => Cond::Less,
153 When::EarlierAndAlreadySet => Cond::LessAndSet,
154 }
155 }
156}
157
158/// Every command that works on a key whatever the key holds.
159///
160/// `DEL`, `EXISTS` and `TYPE`, plus the whole expiry family. Keys are byte
161/// strings the way Redis's are, so anything that is bytes will do.
162///
163/// ```
164/// let db = yo::open(yo::MEMORY)?;
165/// let keys = db.keys();
166///
167/// db.strings().set("greeting", "hello")?;
168/// assert!(keys.exists("greeting")?);
169/// assert!(keys.del("greeting")?);
170/// assert!(!keys.exists("greeting")?);
171/// # Ok::<(), yo::Error>(())
172/// ```
173#[derive(Clone)]
174pub struct Keys {
175 pub(crate) db: Handle,
176}
177
178impl core::fmt::Debug for Keys {
179 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
180 f.debug_struct("Keys").finish_non_exhaustive()
181 }
182}
183
184impl Keys {
185 /// Whether a key is there. `EXISTS`.
186 ///
187 /// A key whose deadline has gone is not there, whether or not anything has
188 /// got around to removing it yet.
189 ///
190 /// # Errors
191 ///
192 /// [`Code::Invalid`] if called from inside a callback that is already
193 /// holding this database.
194 pub fn exists(&self, key: impl AsRef<[u8]>) -> Result<bool> {
195 self.db.run(|inner| Ok(inner.strings.exists(key.as_ref())))
196 }
197
198 /// How many of these keys are there. `EXISTS` with several.
199 ///
200 /// The same key twice counts twice, which is Redis's rule and is worth
201 /// knowing before you use this to count distinct things.
202 ///
203 /// # Errors
204 ///
205 /// As [`Keys::exists`].
206 pub fn count<K: AsRef<[u8]>>(&self, keys: &[K]) -> Result<usize> {
207 self.db.run(|inner| {
208 Ok(keys
209 .iter()
210 .filter(|key| inner.strings.exists(key.as_ref()))
211 .count())
212 })
213 }
214
215 /// What a key holds, or `None` if it holds nothing. `TYPE`.
216 ///
217 /// # Errors
218 ///
219 /// As [`Keys::exists`].
220 pub fn kind(&self, key: impl AsRef<[u8]>) -> Result<Option<Kind>> {
221 self.db.run(|inner| Ok(inner.strings.kind_of(key.as_ref())))
222 }
223
224 /// Remove a key, and say whether it was there. `DEL`.
225 ///
226 /// # Errors
227 ///
228 /// As [`Keys::exists`].
229 pub fn del(&self, key: impl AsRef<[u8]>) -> Result<bool> {
230 self.db.run(|inner| Ok(inner.strings.del(key.as_ref())))
231 }
232
233 /// Remove several keys, and say how many were there. `DEL` with a list.
234 ///
235 /// # Errors
236 ///
237 /// As [`Keys::exists`].
238 pub fn del_many<K: AsRef<[u8]>>(&self, keys: &[K]) -> Result<usize> {
239 self.db.run(|inner| {
240 Ok(keys
241 .iter()
242 .filter(|key| inner.strings.del(key.as_ref()))
243 .count())
244 })
245 }
246
247 /// Give a key this long to live, and say whether the deadline was set.
248 /// `PEXPIRE`.
249 ///
250 /// A duration that has already gone, meaning zero, removes the key and
251 /// answers true, because the deadline was applied and applying it is what
252 /// took the key away. False means the key is not there.
253 ///
254 /// # Errors
255 ///
256 /// [`Code::Invalid`] for a duration that lands past what a millisecond
257 /// timestamp reaches, which is the year 4199, or if called from inside a
258 /// callback that is already holding this database.
259 pub fn expire_in(&self, key: impl AsRef<[u8]>, after: Duration) -> Result<bool> {
260 self.expire_in_when(key, after, When::Always)
261 }
262
263 /// The same with a condition on it. `PEXPIRE` with `NX`, `XX`, `GT` or
264 /// `LT`.
265 ///
266 /// False now means either that the key is not there or that the condition
267 /// said no, which is the one place Redis's reply is genuinely ambiguous.
268 /// Ask [`Keys::ttl`] first if you need to tell them apart.
269 ///
270 /// # Errors
271 ///
272 /// As [`Keys::expire_in`].
273 pub fn expire_in_when(
274 &self,
275 key: impl AsRef<[u8]>,
276 after: Duration,
277 when: When,
278 ) -> Result<bool> {
279 let ms = u64::try_from(after.as_millis()).map_err(|_| too_far())?;
280 self.db.deadlines(|inner| {
281 let at = inner
282 .strings
283 .clock()
284 .now_ms()
285 .checked_add(ms)
286 .ok_or_else(too_far)?;
287 apply(
288 inner
289 .strings
290 .expire(key.as_ref(), reachable(at)?, when.into()),
291 )
292 })
293 }
294
295 /// Set the moment a key goes away, and say whether it was set.
296 /// `PEXPIREAT`.
297 ///
298 /// A moment that has already gone removes the key, the same as
299 /// [`Keys::expire_in`] with nothing left on it.
300 ///
301 /// # Errors
302 ///
303 /// As [`Keys::expire_in`].
304 pub fn expire_at(&self, key: impl AsRef<[u8]>, at: SystemTime) -> Result<bool> {
305 self.expire_at_when(key, at, When::Always)
306 }
307
308 /// The same with a condition on it. `PEXPIREAT` with `NX`, `XX`, `GT` or
309 /// `LT`.
310 ///
311 /// # Errors
312 ///
313 /// As [`Keys::expire_in`].
314 pub fn expire_at_when(
315 &self,
316 key: impl AsRef<[u8]>,
317 at: SystemTime,
318 when: When,
319 ) -> Result<bool> {
320 let ms = moment(at)?;
321 self.db
322 .deadlines(|inner| apply(inner.strings.expire(key.as_ref(), ms, when.into())))
323 }
324
325 /// How long a key has left. `PTTL`.
326 ///
327 /// # Errors
328 ///
329 /// As [`Keys::exists`].
330 pub fn ttl(&self, key: impl AsRef<[u8]>) -> Result<Ttl> {
331 self.db.run(|inner| {
332 let now = inner.strings.clock().now_ms();
333 Ok(match inner.strings.deadline_of(key.as_ref()) {
334 Ask::Missing => Ttl::Missing,
335 Ask::NoDeadline => Ttl::Forever,
336 Ask::At(at) => Ttl::In(Duration::from_millis(at.saturating_sub(now))),
337 })
338 })
339 }
340
341 /// The moment a key goes away, or `None` if it is missing or has no
342 /// deadline. `PEXPIRETIME`.
343 ///
344 /// [`Keys::ttl`] is the one that tells those two apart. This one is for
345 /// when the answer needs to survive being written down, since a moment
346 /// stays true and a duration goes stale as soon as it is read.
347 ///
348 /// # Errors
349 ///
350 /// As [`Keys::exists`].
351 pub fn deadline(&self, key: impl AsRef<[u8]>) -> Result<Option<SystemTime>> {
352 self.db.run(|inner| {
353 Ok(match inner.strings.deadline_of(key.as_ref()) {
354 Ask::At(at) => Some(UNIX_EPOCH + Duration::from_millis(at)),
355 Ask::Missing | Ask::NoDeadline => None,
356 })
357 })
358 }
359
360 /// Take a key's deadline away and let it live, and say whether there was
361 /// one. `PERSIST`.
362 ///
363 /// # Errors
364 ///
365 /// As [`Keys::exists`].
366 pub fn persist(&self, key: impl AsRef<[u8]>) -> Result<bool> {
367 self.db.run(|inner| Ok(inner.strings.persist(key.as_ref())))
368 }
369
370 /// Move a key to another name, over whatever was there. `RENAME`.
371 ///
372 /// The value does not move and is not copied. A set or a hash is a slot
373 /// number sitting in a record, and the same slot number under a different
374 /// key is the same set, so this writes a new record and deletes the old one
375 /// however large the value is. Renaming a set of a million members writes
376 /// thirteen bytes.
377 ///
378 /// The deadline travels with the source, and whatever the destination had
379 /// goes away with the value it belonged to. A key renamed onto itself is
380 /// [`Moved::Ok`] and keeps its deadline.
381 ///
382 /// [`Moved::Taken`] cannot happen here, which is what
383 /// [`Keys::rename_if_new`] is for.
384 ///
385 /// # Errors
386 ///
387 /// As [`Keys::exists`].
388 pub fn rename(&self, src: impl AsRef<[u8]>, dst: impl AsRef<[u8]>) -> Result<Moved> {
389 self.db
390 .run(|inner| Ok(inner.strings.rename(src.as_ref(), dst.as_ref(), false)))
391 }
392
393 /// Move a key to another name, but only if that name is free. `RENAMENX`.
394 ///
395 /// A key renamed onto itself is [`Moved::Taken`], because the destination
396 /// does exist and a key is not new because it is the one you already had.
397 /// That is the one place this and [`Keys::rename`] disagree about a call
398 /// neither of them has to do any work for.
399 ///
400 /// # Errors
401 ///
402 /// As [`Keys::exists`].
403 pub fn rename_if_new(&self, src: impl AsRef<[u8]>, dst: impl AsRef<[u8]>) -> Result<Moved> {
404 self.db
405 .run(|inner| Ok(inner.strings.rename(src.as_ref(), dst.as_ref(), true)))
406 }
407
408 /// Copy a value to another key, leaving the destination alone if it is
409 /// already there. `COPY`.
410 ///
411 /// This is the one call here that costs what the value is worth. Two keys
412 /// cannot share a body, because then adding a member to one would show up in
413 /// the other, so the body is cloned. [`Keys::rename`] is the call that moves
414 /// a large value for nothing, and it is the one to reach for when the old
415 /// name is not wanted afterwards.
416 ///
417 /// The deadline is copied too, so a copy of a key with ten seconds left has
418 /// ten seconds left. A destination whose deadline has already gone counts as
419 /// free.
420 ///
421 /// # Errors
422 ///
423 /// As [`Keys::exists`].
424 pub fn copy(&self, src: impl AsRef<[u8]>, dst: impl AsRef<[u8]>) -> Result<Moved> {
425 self.db
426 .run(|inner| Ok(inner.strings.copy(src.as_ref(), dst.as_ref(), false)))
427 }
428
429 /// Copy a value to another key, over whatever was there. `COPY REPLACE`.
430 ///
431 /// [`Moved::Taken`] cannot happen here, the same way it cannot happen for
432 /// [`Keys::rename`].
433 ///
434 /// # Errors
435 ///
436 /// As [`Keys::exists`].
437 pub fn copy_over(&self, src: impl AsRef<[u8]>, dst: impl AsRef<[u8]>) -> Result<Moved> {
438 self.db
439 .run(|inner| Ok(inner.strings.copy(src.as_ref(), dst.as_ref(), true)))
440 }
441
442 /// Every key in the database, one call each. `KEYS *` without the reply.
443 ///
444 /// The key is handed over where it lies, so a walk of a million keys
445 /// allocates nothing at all. It is only borrowed for the length of the
446 /// call, which is what stops it from outliving the record it points into,
447 /// so anything you want to keep has to be copied out inside the closure.
448 ///
449 /// A key whose deadline has passed is not handed over, and it is deleted
450 /// once the walk has finished, so a walk is also the cheapest way to clear
451 /// out a database that has had a lot of things expire in it.
452 ///
453 /// ```
454 /// let db = yo::open(yo::MEMORY)?;
455 /// db.strings().set("a", "1")?;
456 /// db.strings().set("b", "2")?;
457 ///
458 /// let mut n = 0;
459 /// db.keys().each(|_| n += 1)?;
460 /// assert_eq!(n, 2);
461 /// # Ok::<(), yo::Error>(())
462 /// ```
463 ///
464 /// # Errors
465 ///
466 /// As [`Keys::exists`], which includes calling any method on this database
467 /// from inside the closure.
468 pub fn each(&self, mut f: impl FnMut(&[u8])) -> Result<()> {
469 self.db.run(|inner| {
470 inner.strings.keys(&mut f);
471 Ok(())
472 })
473 }
474
475 /// Every key, copied out into a vector. `KEYS *`.
476 ///
477 /// The convenient one, and the one that costs a key's worth of memory per
478 /// key. [`Keys::each`] is the same walk without that.
479 ///
480 /// # Errors
481 ///
482 /// As [`Keys::exists`].
483 pub fn all(&self) -> Result<Vec<Vec<u8>>> {
484 let mut out = Vec::new();
485 self.each(|key| out.push(key.to_vec()))?;
486 Ok(out)
487 }
488
489 /// Every key matching a glob pattern. `KEYS pattern`.
490 ///
491 /// The same `*`, `?`, `[abc]` and `\` that Redis matches with, so a pattern
492 /// that works against a Redis client works here.
493 ///
494 /// ```
495 /// let db = yo::open(yo::MEMORY)?;
496 /// db.strings().set("user:1", "alice")?;
497 /// db.strings().set("user:2", "bob")?;
498 /// db.strings().set("session:1", "x")?;
499 ///
500 /// assert_eq!(db.keys().matching("user:*")?.len(), 2);
501 /// # Ok::<(), yo::Error>(())
502 /// ```
503 ///
504 /// # Errors
505 ///
506 /// As [`Keys::exists`].
507 pub fn matching(&self, pattern: impl AsRef<[u8]>) -> Result<Vec<Vec<u8>>> {
508 let pattern = pattern.as_ref();
509 let mut out = Vec::new();
510 self.each(|key| {
511 if yo_common::glob_matches(pattern, key) {
512 out.push(key.to_vec());
513 }
514 })?;
515 Ok(out)
516 }
517
518 /// One key, chosen at random, or `None` if the database is empty.
519 /// `RANDOMKEY`.
520 ///
521 /// A constant number of loads whatever the database holds, because it picks
522 /// a place in the index and takes a key from there rather than walking to
523 /// find one.
524 ///
525 /// # Errors
526 ///
527 /// As [`Keys::exists`].
528 pub fn random(&self) -> Result<Option<Vec<u8>>> {
529 // The engine hands back a borrow of its scratch buffer and this is an
530 // owning API, so the copy happens here rather than in there.
531 self.db
532 .run(|inner| Ok(inner.strings.random_key().map(<[u8]>::to_vec)))
533 }
534}
535
536/// Both ways of applying a deadline answer the same question, so they say so in
537/// the same place.
538fn apply(done: Applied) -> Result<bool> {
539 Ok(match done {
540 Applied::Ok | Applied::Deleted => true,
541 Applied::Missing | Applied::NotMet => false,
542 })
543}
544
545/// A wall clock moment as milliseconds since the epoch.
546///
547/// Anything before the epoch is zero, which is a moment that has already gone
548/// and therefore removes the key. That is the same answer `PEXPIREAT key 0`
549/// gets and there is nothing else it could sensibly mean.
550fn moment(at: SystemTime) -> Result<u64> {
551 let ms = at
552 .duration_since(UNIX_EPOCH)
553 .map_or(0, |since| since.as_millis());
554 reachable(u64::try_from(ms).map_err(|_| too_far())?)
555}
556
557/// The wire clamps a deadline past the year 4199 because a real server accepts
558/// the number, which is D-17. Nothing is being answered for here, so this says
559/// no instead.
560fn reachable(at: u64) -> Result<u64> {
561 if at > MAX_AT {
562 return Err(too_far());
563 }
564 Ok(at)
565}
566
567fn too_far() -> Error {
568 Error::new(
569 Code::Invalid,
570 "that deadline is further away than a millisecond timestamp reaches, which is the year 4199",
571 )
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577 use crate::{MEMORY, open};
578
579 #[test]
580 fn a_key_is_there_until_it_is_not() {
581 let db = open(MEMORY).unwrap();
582 let keys = db.keys();
583
584 assert!(!keys.exists("k").unwrap());
585 assert_eq!(keys.kind("k").unwrap(), None);
586 assert!(!keys.del("k").unwrap());
587
588 db.strings().set("k", "v").unwrap();
589 assert!(keys.exists("k").unwrap());
590 assert_eq!(keys.kind("k").unwrap(), Some(Kind::String));
591 assert!(keys.del("k").unwrap());
592 assert!(!keys.exists("k").unwrap());
593 }
594
595 #[test]
596 fn a_rename_carries_the_deadline_and_a_copy_is_a_second_value() {
597 let db = open(MEMORY).unwrap();
598 let keys = db.keys();
599 db.strings().set("a", "v1").unwrap();
600 keys.expire_in("a", Duration::from_secs(100)).unwrap();
601 db.strings().set("b", "v2").unwrap();
602
603 assert_eq!(keys.rename("a", "b").unwrap(), Moved::Ok);
604 assert_eq!(db.strings().get("b").unwrap().as_deref(), Some(&b"v1"[..]));
605 assert!(keys.ttl("b").unwrap().left().is_some(), "a's and not b's");
606 assert!(!keys.exists("a").unwrap());
607
608 db.set("s").add("m1").unwrap();
609 assert_eq!(keys.copy("s", "t").unwrap(), Moved::Ok);
610 db.set("t").add("m2").unwrap();
611 assert_eq!(db.set("s").len().unwrap(), 1, "the original is intact");
612 assert_eq!(db.set("t").len().unwrap(), 2);
613 }
614
615 #[test]
616 fn the_three_answers_a_move_can_give_are_three_and_not_two() {
617 let db = open(MEMORY).unwrap();
618 let keys = db.keys();
619 db.strings().set("a", "v1").unwrap();
620 db.strings().set("b", "v2").unwrap();
621
622 // Missing and Taken both mean nothing happened, and a caller that has
623 // to tell them apart should not need a second call to find out which.
624 assert_eq!(keys.rename_if_new("nosuch", "z").unwrap(), Moved::Missing);
625 assert_eq!(keys.rename_if_new("a", "b").unwrap(), Moved::Taken);
626 assert_eq!(keys.copy("a", "b").unwrap(), Moved::Taken);
627 assert_eq!(db.strings().get("b").unwrap().as_deref(), Some(&b"v2"[..]));
628
629 assert_eq!(keys.copy_over("a", "b").unwrap(), Moved::Ok);
630 assert_eq!(db.strings().get("b").unwrap().as_deref(), Some(&b"v1"[..]));
631 // Onto itself is the one call the two renames disagree about.
632 assert_eq!(keys.rename("a", "a").unwrap(), Moved::Ok);
633 assert_eq!(keys.rename_if_new("a", "a").unwrap(), Moved::Taken);
634 }
635
636 #[test]
637 fn several_keys_at_once_count_the_way_redis_counts_them() {
638 let db = open(MEMORY).unwrap();
639 let keys = db.keys();
640 db.strings().set("a", "1").unwrap();
641 db.strings().set("b", "2").unwrap();
642
643 assert_eq!(keys.count(&["a", "b", "missing"]).unwrap(), 2);
644 assert_eq!(keys.count(&["a", "a"]).unwrap(), 2, "the same key twice");
645 assert_eq!(keys.del_many(&["a", "b", "missing"]).unwrap(), 2);
646 assert_eq!(keys.count(&["a", "b"]).unwrap(), 0);
647 }
648
649 #[test]
650 fn a_deadline_lands_on_a_key_whatever_the_key_holds() {
651 let db = open(MEMORY).unwrap();
652 let keys = db.keys();
653 db.strings().set("s", "v").unwrap();
654 db.set("t").add("member").unwrap();
655
656 for key in ["s", "t"] {
657 assert!(keys.expire_in(key, Duration::from_secs(600)).unwrap());
658 let left = keys.ttl(key).unwrap().left().expect("a deadline");
659 assert!(left <= Duration::from_secs(600) && left > Duration::from_secs(590));
660 }
661
662 assert_eq!(keys.kind("t").unwrap(), Some(Kind::Set), "still a set");
663 assert_eq!(db.set("t").len().unwrap(), 1, "with its member");
664 }
665
666 #[test]
667 fn the_three_answers_are_three_and_not_two() {
668 let db = open(MEMORY).unwrap();
669 let keys = db.keys();
670
671 assert_eq!(keys.ttl("nothing").unwrap(), Ttl::Missing);
672 assert!(!keys.ttl("nothing").unwrap().found());
673
674 db.strings().set("k", "v").unwrap();
675 assert_eq!(keys.ttl("k").unwrap(), Ttl::Forever);
676 assert!(keys.ttl("k").unwrap().found());
677 assert_eq!(keys.ttl("k").unwrap().left(), None, "forever has none left");
678
679 keys.expire_in("k", Duration::from_secs(60)).unwrap();
680 assert!(matches!(keys.ttl("k").unwrap(), Ttl::In(_)));
681 }
682
683 #[test]
684 fn a_moment_that_has_gone_removes_the_key_now() {
685 let db = open(MEMORY).unwrap();
686 let keys = db.keys();
687 db.strings().set("k", "v").unwrap();
688
689 assert!(keys.expire_at("k", UNIX_EPOCH).unwrap(), "it was applied");
690 assert!(!keys.exists("k").unwrap(), "and applying it took the key");
691 }
692
693 #[test]
694 fn a_deadline_comes_back_as_the_moment_it_was_set_to() {
695 let db = open(MEMORY).unwrap();
696 let keys = db.keys();
697 db.strings().set("k", "v").unwrap();
698 assert_eq!(keys.deadline("k").unwrap(), None, "no deadline yet");
699
700 let at = UNIX_EPOCH + Duration::from_millis(4_000_000_000_000);
701 assert!(keys.expire_at("k", at).unwrap());
702 assert_eq!(keys.deadline("k").unwrap(), Some(at));
703
704 assert!(keys.persist("k").unwrap());
705 assert_eq!(keys.deadline("k").unwrap(), None);
706 assert!(
707 !keys.persist("k").unwrap(),
708 "there was nothing left to take"
709 );
710 assert!(keys.exists("k").unwrap(), "and the key is still here");
711 }
712
713 #[test]
714 fn a_condition_decides_whether_the_deadline_moves() {
715 let db = open(MEMORY).unwrap();
716 let keys = db.keys();
717 db.strings().set("k", "v").unwrap();
718
719 let hour = Duration::from_secs(3600);
720 let day = Duration::from_secs(86400);
721
722 assert!(keys.expire_in_when("k", hour, When::Unset).unwrap());
723 assert!(
724 !keys.expire_in_when("k", day, When::Unset).unwrap(),
725 "taken"
726 );
727 assert!(keys.expire_in_when("k", day, When::AlreadySet).unwrap());
728 assert!(!keys.expire_in_when("k", hour, When::Later).unwrap(), "in");
729 assert!(keys.expire_in_when("k", hour, When::Earlier).unwrap());
730 assert!(keys.expire_in_when("k", day, When::Later).unwrap());
731
732 keys.persist("k").unwrap();
733 assert!(
734 !keys.expire_in_when("k", hour, When::Later).unwrap(),
735 "no deadline is infinitely far out, so nothing is further"
736 );
737 assert!(
738 keys.expire_in_when("k", hour, When::Earlier).unwrap(),
739 "and by the same reading everything is nearer"
740 );
741 keys.persist("k").unwrap();
742 assert!(
743 !keys
744 .expire_in_when("k", hour, When::EarlierAndAlreadySet)
745 .unwrap(),
746 "unless XX takes that reading away"
747 );
748 }
749
750 #[test]
751 fn a_condition_that_says_no_leaves_a_key_that_would_have_gone() {
752 let db = open(MEMORY).unwrap();
753 let keys = db.keys();
754 db.strings().set("k", "v").unwrap();
755 keys.expire_in("k", Duration::from_secs(60)).unwrap();
756
757 assert!(
758 !keys.expire_at_when("k", UNIX_EPOCH, When::Unset).unwrap(),
759 "the condition is checked before the moment is"
760 );
761 assert!(keys.exists("k").unwrap());
762 }
763
764 #[test]
765 fn a_deadline_past_the_year_4199_is_refused_rather_than_clamped() {
766 let db = open(MEMORY).unwrap();
767 let keys = db.keys();
768 db.strings().set("k", "v").unwrap();
769
770 let err = keys
771 .expire_in("k", Duration::from_secs(u64::MAX))
772 .unwrap_err();
773 assert_eq!(err.code(), Code::Invalid);
774 let far = UNIX_EPOCH + Duration::from_millis(MAX_AT + 1);
775 assert_eq!(keys.expire_at("k", far).unwrap_err().code(), Code::Invalid);
776 assert_eq!(keys.ttl("k").unwrap(), Ttl::Forever, "and nothing moved");
777 }
778
779 #[test]
780 fn nothing_reads_the_clock_until_a_deadline_exists_to_read_it_for() {
781 let db = open(MEMORY).unwrap();
782 let keys = db.keys();
783 db.strings().set("k", "v").unwrap();
784
785 keys.exists("k").unwrap();
786 keys.kind("k").unwrap();
787 keys.ttl("k").unwrap();
788 assert!(!db.reads_the_clock(), "asking is not creating");
789
790 keys.expire_in("k", Duration::from_secs(60)).unwrap();
791 assert!(db.reads_the_clock());
792 }
793
794 #[test]
795 fn a_walk_sees_every_key_whatever_it_holds() {
796 let db = open(MEMORY).unwrap();
797 let keys = db.keys();
798 assert!(keys.all().unwrap().is_empty());
799 assert_eq!(keys.random().unwrap(), None);
800
801 db.strings().set("a", "v").unwrap();
802 db.set("s").add("m").unwrap();
803 for i in 0..1_000 {
804 db.strings().set(format!("n:{i}"), "v").unwrap();
805 }
806
807 let mut all = keys.all().unwrap();
808 all.sort();
809 assert_eq!(all.len(), 1_002);
810 assert_eq!(all[0], b"a");
811 assert_eq!(keys.matching("n:*").unwrap().len(), 1_000);
812 assert_eq!(keys.matching("s").unwrap(), vec![b"s".to_vec()]);
813 assert!(keys.matching("nothing").unwrap().is_empty());
814
815 // A random key is one of the keys, and not the same one every time.
816 let mut picked = std::collections::HashSet::new();
817 for _ in 0..100 {
818 picked.insert(keys.random().unwrap().expect("the database is not empty"));
819 }
820 assert!(
821 picked.len() > 5,
822 "randomkey is stuck on {} keys",
823 picked.len()
824 );
825 assert!(picked.iter().all(|k| all.contains(k)));
826 }
827
828 #[test]
829 fn a_walk_does_not_hand_out_a_key_that_has_expired() {
830 let db = open(MEMORY).unwrap();
831 let keys = db.keys();
832 db.strings().set("alive", "v").unwrap();
833 db.strings().set("dead", "v").unwrap();
834 keys.expire_at("dead", UNIX_EPOCH + Duration::from_secs(1))
835 .unwrap();
836
837 assert_eq!(keys.all().unwrap(), vec![b"alive".to_vec()]);
838 assert_eq!(keys.random().unwrap(), Some(b"alive".to_vec()));
839 }
840
841 /// The closure holds the database, so a call back into it from inside the
842 /// walk is refused rather than deadlocked or, worse, allowed.
843 #[test]
844 fn a_walk_cannot_be_reentered() {
845 let db = open(MEMORY).unwrap();
846 let keys = db.keys();
847 db.strings().set("k", "v").unwrap();
848
849 let mut inner = Ok(true);
850 keys.each(|_| inner = keys.exists("k")).unwrap();
851 assert_eq!(inner.unwrap_err().code(), Code::Invalid);
852 }
853}