1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! Every command that writes its result somewhere must mark that destination as
//! a key.
//!
//! A destination added with `arg` takes no part in slot computation, so the
//! command routes on its source keys alone and the client-side
//! `MismatchedKeySlots` guard cannot see the destination at all: a destination
//! owned by another node is sent and refused by the server instead of being
//! refused locally, and where the sources are keyless the write lands on a
//! random node.
//!
//! The commands are built against a stand-in executor rather than a client, the
//! question being what the builder marks and not what a server answers.
use crate::{
client::PreparedCommand,
commands::{
GenericCommands, SetCommands, SortOptions, SortedSetCommands, StringCommands, ZAggregate,
},
};
use serde::de::DeserializeOwned;
/// Stands in for the executor a command is prepared against. The traits are
/// implemented for any `Sized` receiver, and nothing here reaches the network.
struct Executor;
impl<'a> SetCommands<'a> for &'a Executor {}
impl<'a> SortedSetCommands<'a> for &'a Executor {}
impl<'a> GenericCommands<'a> for &'a Executor {}
impl<'a> StringCommands<'a> for &'a Executor {}
/// The keys a prepared command marks for routing, in the order it wrote them.
fn keys<E, R: DeserializeOwned>(prepared: PreparedCommand<'_, E, R>) -> Vec<String> {
prepared
.command()
.keys()
.map(|key| String::from_utf8_lossy(key.as_ref()).into_owned())
.collect()
}
#[test]
fn a_set_store_command_marks_its_destination() {
let executor = &Executor;
assert_eq!(
vec!["dest", "src1", "src2"],
keys(executor.sdiffstore("dest", ["src1", "src2"]))
);
assert_eq!(
vec!["dest", "src1", "src2"],
keys(executor.sinterstore("dest", ["src1", "src2"]))
);
// The reference: this one has always marked it.
assert_eq!(
vec!["dest", "src1", "src2"],
keys(executor.sunionstore("dest", ["src1", "src2"]))
);
}
#[test]
fn a_sorted_set_store_command_marks_its_destination() {
let executor = &Executor;
assert_eq!(
vec!["dest", "src1", "src2"],
keys(executor.zdiffstore("dest", ["src1", "src2"]))
);
assert_eq!(
vec!["dest", "src1", "src2"],
keys(executor.zinterstore("dest", ["src1", "src2"], None::<f64>, ZAggregate::Sum))
);
assert_eq!(
vec!["dest", "src1", "src2"],
keys(executor.zunionstore("dest", ["src1", "src2"], None::<f64>, ZAggregate::Sum))
);
}
/// `SORT … STORE` differs in shape: the destination follows the `STORE` token
/// rather than opening the command, and the source is a single key.
#[test]
fn sort_and_store_marks_its_destination() {
let executor = &Executor;
assert_eq!(
vec!["src", "dest"],
keys(executor.sort_and_store("src", "dest", SortOptions::default()))
);
}
/// `LCS` writes nothing, but it still reads two keys, and both must be routed
/// on: a second key in another slot is a `CROSSSLOT` the client can refuse
/// before the round trip. Its three forms take the same pair.
#[test]
fn every_lcs_form_marks_both_of_its_keys() {
let executor = &Executor;
assert_eq!(
vec!["key1", "key2"],
keys(executor.lcs::<String>("key1", "key2"))
);
assert_eq!(vec!["key1", "key2"], keys(executor.lcs_len("key1", "key2")));
assert_eq!(
vec!["key1", "key2"],
keys(executor.lcs_idx("key1", "key2", None, false))
);
}