1use std::{
5 collections::{BTreeMap, BTreeSet},
6 ops::Deref,
7 sync::{Arc, OnceLock},
8};
9
10use reifydb_codec::{key::encoded::EncodedKey, row::bytes::EncodedBytes};
11use reifydb_value::value::{datetime::DateTime, row_number::RowNumber};
12use serde::{Deserialize, Deserializer, Serialize, Serializer};
13
14use crate::{
15 common::{ChangeVersion, CommitVersion},
16 interface::{catalog::object::ObjectId, change::Change},
17};
18
19#[repr(transparent)]
20#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
21pub struct CdcConsumerId(pub(crate) String);
22
23impl CdcConsumerId {
24 const FLOW: &'static str = "__FLOW_COORDINATOR";
25 const SUBSCRIPTION: &'static str = "__SUBSCRIPTION_CONSUMER";
26
27 pub fn new(id: impl Into<String>) -> Self {
28 let id = id.into();
29 assert_ne!(id, Self::FLOW);
30 assert_ne!(id, Self::SUBSCRIPTION);
31 Self(id)
32 }
33
34 pub fn flow_consumer() -> Self {
35 Self(Self::FLOW.to_string())
36 }
37
38 pub fn subscription_consumer() -> Self {
39 Self(Self::SUBSCRIPTION.to_string())
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum ConsumerClass {
45 Pinning,
46 Ephemeral,
47}
48
49impl ConsumerClass {
50 pub fn encode(self) -> u8 {
51 match self {
52 Self::Pinning => 0,
53 Self::Ephemeral => 1,
54 }
55 }
56
57 pub fn decode(byte: u8) -> Option<Self> {
58 match byte {
59 0 => Some(Self::Pinning),
60 1 => Some(Self::Ephemeral),
61 _ => None,
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum CheckpointState {
68 Valid,
69 Invalidated,
70}
71
72impl CheckpointState {
73 pub fn encode(self) -> u8 {
74 match self {
75 Self::Valid => 0,
76 Self::Invalidated => 1,
77 }
78 }
79
80 pub fn decode(byte: u8) -> Option<Self> {
81 match byte {
82 0 => Some(Self::Valid),
83 1 => Some(Self::Invalidated),
84 _ => None,
85 }
86 }
87}
88
89impl AsRef<str> for CdcConsumerId {
90 fn as_ref(&self) -> &str {
91 &self.0
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96pub enum CdcChange {
97 Insert {
98 key: EncodedKey,
99 post: EncodedBytes,
100 },
101 Update {
102 key: EncodedKey,
103 pre: EncodedBytes,
104 post: EncodedBytes,
105 },
106 Delete {
107 key: EncodedKey,
108 pre: Option<EncodedBytes>,
109 visible: bool,
110 },
111}
112
113impl CdcChange {
114 pub fn key(&self) -> &EncodedKey {
115 match self {
116 CdcChange::Insert {
117 key,
118 ..
119 } => key,
120 CdcChange::Update {
121 key,
122 ..
123 } => key,
124 CdcChange::Delete {
125 key,
126 ..
127 } => key,
128 }
129 }
130
131 pub fn value_bytes(&self) -> usize {
132 match self {
133 CdcChange::Insert {
134 post,
135 ..
136 } => post.len(),
137 CdcChange::Update {
138 pre,
139 post,
140 ..
141 } => pre.len() + post.len(),
142 CdcChange::Delete {
143 pre,
144 ..
145 } => pre.as_ref().map(|p| p.len()).unwrap_or(0),
146 }
147 }
148}
149
150#[derive(Debug, Clone)]
151pub struct Cdc(Arc<CdcRecord>);
152
153#[derive(Debug, Serialize, Deserialize)]
154pub struct CdcRecord {
155 pub version: ChangeVersion,
156 pub timestamp: DateTime,
157
158 pub changes: Vec<CdcChange>,
159
160 #[serde(skip)]
161 changed_objects: OnceLock<BTreeSet<ObjectId>>,
162
163 #[serde(skip)]
164 lift: OnceLock<CdcLift>,
165}
166
167#[derive(Debug, Clone, Default)]
168pub struct CdcLift {
169 pub objects: BTreeMap<ObjectId, ObjectLift>,
170}
171
172#[derive(Debug, Clone, Default)]
173pub struct ObjectLift {
174 pub rows: Vec<(usize, RowNumber)>,
175 pub first_missing_pre: Option<usize>,
176 pub change: OnceLock<Option<Change>>,
177}
178
179impl Cdc {
180 pub fn new(version: ChangeVersion, timestamp: DateTime, changes: Vec<CdcChange>) -> Self {
181 Self(Arc::new(CdcRecord {
182 version,
183 timestamp,
184 changes,
185 changed_objects: OnceLock::new(),
186 lift: OnceLock::new(),
187 }))
188 }
189
190 pub fn changed_objects_with(&self, compute: impl FnOnce(&Cdc) -> BTreeSet<ObjectId>) -> &BTreeSet<ObjectId> {
191 self.0.changed_objects.get_or_init(|| compute(self))
192 }
193
194 pub fn lift_with(&self, build: impl FnOnce(&Cdc) -> CdcLift) -> &CdcLift {
195 self.0.lift.get_or_init(|| build(self))
196 }
197}
198
199impl Deref for Cdc {
200 type Target = CdcRecord;
201
202 fn deref(&self) -> &CdcRecord {
203 &self.0
204 }
205}
206
207impl Serialize for Cdc {
208 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
209 self.0.serialize(serializer)
210 }
211}
212
213impl<'de> Deserialize<'de> for Cdc {
214 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
215 CdcRecord::deserialize(deserializer).map(|record| Self(Arc::new(record)))
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct ConsumerState {
221 pub consumer_id: CdcConsumerId,
222 pub checkpoint: CommitVersion,
223}
224
225#[derive(Debug, Clone)]
226pub struct CdcBatch {
227 pub items: Vec<Cdc>,
228
229 pub has_more: bool,
230}
231
232impl CdcBatch {
233 pub fn empty() -> Self {
234 Self {
235 items: Vec::new(),
236 has_more: false,
237 }
238 }
239
240 pub fn is_empty(&self) -> bool {
241 self.items.is_empty()
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use std::cell::Cell;
248
249 use super::*;
250 use crate::interface::catalog::id::TableId;
251
252 #[test]
253 fn changed_objects_are_computed_once_per_commit() {
254 let cdc = Cdc::new(ChangeVersion::from(CommitVersion(1)), DateTime::from_nanos(1), Vec::new());
257 let calls = Cell::new(0);
258 let first = BTreeSet::from([ObjectId::Table(TableId(1))]);
259
260 let a = cdc.changed_objects_with(|_| {
261 calls.set(calls.get() + 1);
262 first.clone()
263 });
264 assert_eq!(a, &first);
265 let b = cdc.changed_objects_with(|_| {
266 calls.set(calls.get() + 1);
267 BTreeSet::from([ObjectId::Table(TableId(2))])
268 });
269
270 assert_eq!(calls.get(), 1, "the second call recomputed instead of reading the memo");
271 assert_eq!(b, &first, "the memoised set was replaced by the second compute");
272 }
273}