Skip to main content

dynomite/proto/memcache/
repair.rs

1//! Memcached repair surface.
2//!
3//! In the reference engine, every `memcache_*_repair` function and
4//! `memcache_reconcile_responses` either returns `DN_OK` after doing
5//! nothing or, in the reconciliation case, returns the first
6//! response when consistency is `DC_QUORUM` and an error response
7//! otherwise. Memcached responses are not rewritten, so the
8//! "rewrite with metadata" surface is intentionally empty.
9//!
10//! The Rust port reproduces this exactly. Each function below
11//! mirrors the reference shape for parity with the Redis surface
12//! and so the cluster layer can call into either protocol via the
13//! same trait set.
14
15#[cfg(test)]
16use crate::msg::MsgType;
17use crate::msg::{ConsistencyLevel, DynErrorCode, Msg, ResponseMgr};
18
19/// Repair-surface result type. Matches the Redis repair return
20/// shape so the cluster dispatcher can use a single
21/// `Result<RepairOutcome, RepairError>` discriminant.
22#[derive(Debug)]
23pub enum RepairOutcome {
24    /// No rewrite or repair message produced.
25    NoOp,
26    /// A rewritten message (Memcached path never produces one).
27    Rewritten(Box<Msg>),
28}
29
30/// Errors the Memcached repair surface can raise. Memcached has no
31/// live failure modes; the variant exists for parity with the
32/// Redis surface.
33#[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)]
34#[non_exhaustive]
35pub enum RepairError {
36    /// Reserved variant.
37    #[error("memcache repair: reserved")]
38    Reserved,
39}
40
41/// Memcached query rewrite. The reference engine returns `DN_OK`
42/// without producing a rewritten message. The Rust port returns
43/// [`RepairOutcome::NoOp`] for the same reason.
44///
45/// # Examples
46///
47/// ```
48/// use dynomite::msg::{Msg, MsgType};
49/// use dynomite::proto::memcache::memcache_rewrite_query;
50///
51/// let mut m = Msg::new(0, MsgType::ReqMcSet, true);
52/// let outcome = memcache_rewrite_query(&mut m).unwrap();
53/// matches!(outcome, dynomite::proto::memcache::repair::RepairOutcome::NoOp);
54/// ```
55pub fn memcache_rewrite_query(_orig: &mut Msg) -> Result<RepairOutcome, RepairError> {
56    Ok(RepairOutcome::NoOp)
57}
58
59/// Memcached query rewrite with timestamp metadata. The reference
60/// engine returns `DN_OK` without rewriting; the Rust port matches.
61///
62/// # Examples
63///
64/// ```
65/// use dynomite::msg::{Msg, MsgType};
66/// use dynomite::proto::memcache::memcache_rewrite_query_with_timestamp_md;
67///
68/// let mut m = Msg::new(0, MsgType::ReqMcSet, true);
69/// assert!(memcache_rewrite_query_with_timestamp_md(&mut m).is_ok());
70/// ```
71pub fn memcache_rewrite_query_with_timestamp_md(
72    _orig: &mut Msg,
73) -> Result<RepairOutcome, RepairError> {
74    Ok(RepairOutcome::NoOp)
75}
76
77/// Build a repair query for a Memcached response set. The reference
78/// engine returns `DN_OK` without producing a repair query. The
79/// Rust port returns [`RepairOutcome::NoOp`].
80///
81/// # Examples
82///
83/// ```
84/// use dynomite::msg::{Msg, MsgType, ResponseMgr};
85/// use dynomite::proto::memcache::memcache_make_repair_query;
86///
87/// let req = Msg::new(0, MsgType::ReqMcGet, true);
88/// let mgr = ResponseMgr::new(&req, 1, None);
89/// assert!(memcache_make_repair_query(&mgr).is_ok());
90/// ```
91pub fn memcache_make_repair_query(_rspmgr: &ResponseMgr) -> Result<RepairOutcome, RepairError> {
92    Ok(RepairOutcome::NoOp)
93}
94
95/// Clear repair metadata for a key. The reference engine returns
96/// `DN_OK` without producing a cleanup message. The Rust port
97/// matches.
98///
99/// # Examples
100///
101/// ```
102/// use dynomite::msg::{Msg, MsgType};
103/// use dynomite::proto::memcache::memcache_clear_repair_md_for_key;
104///
105/// let mut req = Msg::new(0, MsgType::ReqMcSet, true);
106/// assert!(memcache_clear_repair_md_for_key(&mut req).is_ok());
107/// ```
108pub fn memcache_clear_repair_md_for_key(_req: &mut Msg) -> Result<RepairOutcome, RepairError> {
109    Ok(RepairOutcome::NoOp)
110}
111
112/// Reconcile responses across replicas. The reference engine picks
113/// the first response under `DC_QUORUM` and otherwise returns an
114/// error response. The Rust port reproduces both arms by reporting
115/// the chosen index plus an optional fresh error response.
116///
117/// # Examples
118///
119/// ```
120/// use dynomite::msg::{ConsistencyLevel, Msg, MsgType, ResponseMgr};
121/// use dynomite::proto::memcache::memcache_reconcile_responses;
122///
123/// let mut req = Msg::new(0, MsgType::ReqMcGet, true);
124/// req.set_consistency(ConsistencyLevel::DcQuorum);
125/// let mut mgr = ResponseMgr::new(&req, 3, None);
126/// // Submit one response (Stage 9 plumbing supplies the responses
127/// // in real use).
128/// let outcome = memcache_reconcile_responses(&mgr, ConsistencyLevel::DcQuorum);
129/// matches!(outcome, dynomite::proto::memcache::repair::ReconcileOutcome::PickFirst);
130/// ```
131pub fn memcache_reconcile_responses(
132    _rspmgr: &ResponseMgr,
133    consistency: ConsistencyLevel,
134) -> ReconcileOutcome {
135    match consistency {
136        ConsistencyLevel::DcQuorum => ReconcileOutcome::PickFirst,
137        _ => ReconcileOutcome::Error(DynErrorCode::DynomiteNoQuorumAchieved),
138    }
139}
140
141/// Outcome of [`memcache_reconcile_responses`].
142#[derive(Copy, Clone, Debug, Eq, PartialEq)]
143pub enum ReconcileOutcome {
144    /// Return the first response in the response manager.
145    PickFirst,
146    /// Allocate and return a fresh error response with the given
147    /// error code.
148    Error(DynErrorCode),
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn rewrite_is_noop() {
157        let mut m = Msg::new(0, MsgType::ReqMcSet, true);
158        let outcome = memcache_rewrite_query(&mut m).unwrap();
159        assert!(matches!(outcome, RepairOutcome::NoOp));
160    }
161
162    #[test]
163    fn reconcile_quorum_picks_first() {
164        let req = Msg::new(0, MsgType::ReqMcGet, true);
165        let mgr = ResponseMgr::new(&req, 3, None);
166        let outcome = memcache_reconcile_responses(&mgr, ConsistencyLevel::DcQuorum);
167        assert_eq!(outcome, ReconcileOutcome::PickFirst);
168    }
169
170    #[test]
171    fn reconcile_safe_quorum_emits_error() {
172        let req = Msg::new(0, MsgType::ReqMcGet, true);
173        let mgr = ResponseMgr::new(&req, 3, None);
174        let outcome = memcache_reconcile_responses(&mgr, ConsistencyLevel::DcSafeQuorum);
175        assert_eq!(
176            outcome,
177            ReconcileOutcome::Error(DynErrorCode::DynomiteNoQuorumAchieved),
178        );
179    }
180}