Skip to main content

reifydb_engine/transaction/operation/
ringbuffer.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_catalog::catalog::Catalog;
5use reifydb_codec::{
6	key::encoded::EncodedKey,
7	row::{
8		bytes::{EncodedBytes, RowBuilder},
9		ringbuffer::EncodedRingBufferRow,
10		shape::{RowFamily, RowShape},
11	},
12};
13use reifydb_core::{
14	common::CommitVersion,
15	interface::{
16		catalog::{
17			object::ObjectId,
18			ringbuffer::{RingBuffer, RingBufferMetadata},
19		},
20		change::{Change, ChangeOrigin, Diff},
21	},
22	key::{
23		partitioned_row::{PartitionedRowKey, RowLocator},
24		row::RowKey,
25	},
26	partition::{PartitionError, partition_col_indices},
27	row::row_shape_from_columns,
28	value::column::columns::Columns,
29};
30use reifydb_transaction::{
31	interceptor::ringbuffer_row::RingBufferRowInterceptor,
32	transaction::{Transaction, admin::AdminTransaction, command::CommandTransaction},
33};
34use reifydb_value::{
35	util::cowvec::CowVec,
36	value::{Value, datetime::DateTime, partition::Partition, row_number::RowNumber},
37};
38use smallvec::smallvec;
39
40use crate::{Result, partition::partition_values};
41
42fn ringbuffer_key(ringbuffer: &RingBuffer, partition: Option<Partition>, row_number: RowNumber) -> EncodedKey {
43	match partition {
44		None => RowKey::encoded(ringbuffer.id, row_number),
45		Some(partition) => PartitionedRowKey::encoded(
46			ObjectId::ringbuffer(ringbuffer.id),
47			partition,
48			RowLocator::Row(row_number),
49		),
50	}
51}
52
53fn build_ringbuffer_insert_change(
54	rb: &RingBuffer,
55	shape: &RowShape,
56	row_number: RowNumber,
57	encoded: &EncodedBytes,
58) -> Change {
59	let ids = [row_number];
60	let rows = [encoded.clone()];
61	Change {
62		origin: ChangeOrigin::Object(ObjectId::ringbuffer(rb.id)),
63		version: CommitVersion(0),
64		diffs: smallvec![Diff::insert(Columns::from_encoded_bytes(shape, &ids, &rows))],
65		changed_at: DateTime::default(),
66	}
67}
68
69fn build_ringbuffer_update_change(
70	rb: &RingBuffer,
71	row_number: RowNumber,
72	pre: &EncodedBytes,
73	post: &EncodedBytes,
74) -> Change {
75	let shape = row_shape_from_columns(RowFamily::RingBuffer, &rb.columns);
76	let ids = [row_number];
77	let pres = [pre.clone()];
78	let posts = [post.clone()];
79	Change {
80		origin: ChangeOrigin::Object(ObjectId::ringbuffer(rb.id)),
81		version: CommitVersion(0),
82		diffs: smallvec![Diff::update(
83			Columns::from_encoded_bytes(&shape, &ids, &pres),
84			Columns::from_encoded_bytes(&shape, &ids, &posts),
85		)],
86		changed_at: DateTime::default(),
87	}
88}
89
90fn build_ringbuffer_remove_change(rb: &RingBuffer, row_number: RowNumber, encoded: &EncodedBytes) -> Change {
91	let shape = row_shape_from_columns(RowFamily::RingBuffer, &rb.columns);
92	let ids = [row_number];
93	let rows = [encoded.clone()];
94	Change {
95		origin: ChangeOrigin::Object(ObjectId::ringbuffer(rb.id)),
96		version: CommitVersion(0),
97		diffs: smallvec![Diff::remove(Columns::from_encoded_bytes(&shape, &ids, &rows))],
98		changed_at: DateTime::default(),
99	}
100}
101
102pub fn apply_ringbuffer_partition_metadata_after_delete(
103	catalog: &Catalog,
104	txn: &mut Transaction<'_>,
105	ringbuffer: &RingBuffer,
106	partition_key: &[Value],
107	mut partition: RingBufferMetadata,
108	deleted: u64,
109	min_remaining_row: Option<u64>,
110) -> Result<()> {
111	if deleted == 0 {
112		return Ok(());
113	}
114	let remaining_count = partition.count.saturating_sub(deleted);
115	if remaining_count == 0 {
116		catalog.remove_partition_metadata(txn, ringbuffer, partition_key)
117	} else {
118		partition.count = remaining_count;
119		partition.head = min_remaining_row.unwrap();
120		catalog.save_partition_metadata(txn, ringbuffer, partition_key, &partition)
121	}
122}
123
124pub trait RingBufferOperations {
125	fn insert_ringbuffer(&mut self, ringbuffer: RingBuffer, bytes: EncodedBytes) -> Result<RowNumber>;
126
127	fn insert_ringbuffer_at(
128		&mut self,
129		ringbuffer: &RingBuffer,
130		shape: &RowShape,
131		partition: Option<Partition>,
132		row_number: RowNumber,
133		bytes: EncodedBytes,
134	) -> Result<EncodedBytes>;
135
136	fn update_ringbuffer(
137		&mut self,
138		ringbuffer: RingBuffer,
139		partition: Option<Partition>,
140		id: RowNumber,
141		bytes: EncodedBytes,
142	) -> Result<EncodedBytes>;
143
144	fn remove_from_ringbuffer(
145		&mut self,
146		ringbuffer: &RingBuffer,
147		partition: Option<Partition>,
148		id: RowNumber,
149	) -> Result<EncodedBytes>;
150}
151
152impl RingBufferOperations for CommandTransaction {
153	fn insert_ringbuffer(&mut self, _ringbuffer: RingBuffer, _row: EncodedBytes) -> Result<RowNumber> {
154		unimplemented!(
155			"Ring buffer insert must be called with explicit row_number through insert_ringbuffer_at"
156		)
157	}
158
159	fn insert_ringbuffer_at(
160		&mut self,
161		ringbuffer: &RingBuffer,
162		shape: &RowShape,
163		partition: Option<Partition>,
164		row_number: RowNumber,
165		bytes: EncodedBytes,
166	) -> Result<EncodedBytes> {
167		let key = ringbuffer_key(ringbuffer, partition, row_number);
168
169		let pre = self.get(&key)?.map(|v| v.bytes);
170
171		if let Some(ref existing) = pre {
172			let ids = [row_number];
173			let existing_rows = [existing.clone()];
174			RingBufferRowInterceptor::pre_delete(self, ringbuffer, &ids)?;
175			RingBufferRowInterceptor::post_delete(self, ringbuffer, &ids, &existing_rows)?;
176		}
177
178		let mut rows_buf = [EncodedRingBufferRow::from(bytes.clone()).thaw()];
179		RingBufferRowInterceptor::pre_insert(self, ringbuffer, &mut rows_buf)?;
180		let [bytes] = rows_buf;
181		let bytes = bytes.freeze_bytes();
182
183		self.set(&key, bytes.clone())?;
184
185		let ids = [row_number];
186		let rows = [bytes.clone()];
187		RingBufferRowInterceptor::post_insert(self, ringbuffer, &ids, &rows)?;
188
189		if let Some(pre_row) = pre.as_ref() {
190			self.track_flow_change(build_ringbuffer_update_change(ringbuffer, row_number, pre_row, &bytes));
191		} else {
192			self.track_flow_change(build_ringbuffer_insert_change(ringbuffer, shape, row_number, &bytes));
193		}
194
195		Ok(bytes)
196	}
197
198	fn update_ringbuffer(
199		&mut self,
200		ringbuffer: RingBuffer,
201		partition: Option<Partition>,
202		id: RowNumber,
203		bytes: EncodedBytes,
204	) -> Result<EncodedBytes> {
205		let key = ringbuffer_key(&ringbuffer, partition, id);
206
207		let pre = match self.get(&key)? {
208			Some(v) => v.bytes,
209			None => return Ok(bytes),
210		};
211
212		let mut rows_buf = [EncodedRingBufferRow::from(bytes.clone()).thaw()];
213		let ids = [id];
214		RingBufferRowInterceptor::pre_update(self, &ringbuffer, &ids, &mut rows_buf)?;
215		let [bytes] = rows_buf;
216		let bytes = bytes.freeze_bytes();
217
218		if let Some(expected) = partition {
219			let shape = row_shape_from_columns(RowFamily::RingBuffer, &ringbuffer.columns);
220			let indices = partition_col_indices(&ringbuffer.columns, &ringbuffer.partition_by);
221			if Partition::of(&partition_values(&shape, &bytes, &indices)) != expected {
222				return Err(PartitionError::ImmutablePartitionColumn {
223					object: ObjectId::ringbuffer(ringbuffer.id),
224				}
225				.into());
226			}
227		}
228
229		if self.get_committed(&key)?.is_some() {
230			self.mark_preexisting(&key)?;
231		}
232		self.set(&key, bytes.clone())?;
233
234		let posts = [bytes.clone()];
235		let pres = [pre.clone()];
236		RingBufferRowInterceptor::post_update(self, &ringbuffer, &ids, &posts, &pres)?;
237
238		self.track_flow_change(build_ringbuffer_update_change(&ringbuffer, id, &pre, &bytes));
239
240		Ok(bytes)
241	}
242
243	fn remove_from_ringbuffer(
244		&mut self,
245		ringbuffer: &RingBuffer,
246		partition: Option<Partition>,
247		id: RowNumber,
248	) -> Result<EncodedBytes> {
249		let key = ringbuffer_key(ringbuffer, partition, id);
250
251		let displayed = match self.get(&key)? {
252			Some(v) => v.bytes,
253			None => return Ok(EncodedBytes(CowVec::new(vec![]))),
254		};
255		let committed = self.get_committed(&key)?.map(|v| v.bytes);
256
257		let ids = [id];
258		RingBufferRowInterceptor::pre_delete(self, ringbuffer, &ids)?;
259
260		let pre_for_cdc = committed.clone().unwrap_or_else(|| displayed.clone());
261
262		if committed.is_some() {
263			self.mark_preexisting(&key)?;
264		}
265		self.remove_with_pre(&key, pre_for_cdc.clone())?;
266
267		let pre_rows = [pre_for_cdc.clone()];
268		RingBufferRowInterceptor::post_delete(self, ringbuffer, &ids, &pre_rows)?;
269
270		self.track_flow_change(build_ringbuffer_remove_change(ringbuffer, id, &pre_for_cdc));
271
272		Ok(displayed)
273	}
274}
275
276impl RingBufferOperations for AdminTransaction {
277	fn insert_ringbuffer(&mut self, _ringbuffer: RingBuffer, _row: EncodedBytes) -> Result<RowNumber> {
278		unimplemented!(
279			"Ring buffer insert must be called with explicit row_number through insert_ringbuffer_at"
280		)
281	}
282
283	fn insert_ringbuffer_at(
284		&mut self,
285		ringbuffer: &RingBuffer,
286		shape: &RowShape,
287		partition: Option<Partition>,
288		row_number: RowNumber,
289		bytes: EncodedBytes,
290	) -> Result<EncodedBytes> {
291		let key = ringbuffer_key(ringbuffer, partition, row_number);
292
293		let pre = self.get(&key)?.map(|v| v.bytes);
294
295		if let Some(ref existing) = pre {
296			let ids = [row_number];
297			let existing_rows = [existing.clone()];
298			RingBufferRowInterceptor::pre_delete(self, ringbuffer, &ids)?;
299			RingBufferRowInterceptor::post_delete(self, ringbuffer, &ids, &existing_rows)?;
300		}
301
302		let mut rows_buf = [EncodedRingBufferRow::from(bytes.clone()).thaw()];
303		RingBufferRowInterceptor::pre_insert(self, ringbuffer, &mut rows_buf)?;
304		let [bytes] = rows_buf;
305		let bytes = bytes.freeze_bytes();
306
307		self.set(&key, bytes.clone())?;
308
309		let ids = [row_number];
310		let rows = [bytes.clone()];
311		RingBufferRowInterceptor::post_insert(self, ringbuffer, &ids, &rows)?;
312
313		if let Some(pre_row) = pre.as_ref() {
314			self.track_flow_change(build_ringbuffer_update_change(ringbuffer, row_number, pre_row, &bytes));
315		} else {
316			self.track_flow_change(build_ringbuffer_insert_change(ringbuffer, shape, row_number, &bytes));
317		}
318
319		Ok(bytes)
320	}
321
322	fn update_ringbuffer(
323		&mut self,
324		ringbuffer: RingBuffer,
325		partition: Option<Partition>,
326		id: RowNumber,
327		bytes: EncodedBytes,
328	) -> Result<EncodedBytes> {
329		let key = ringbuffer_key(&ringbuffer, partition, id);
330
331		let pre = match self.get(&key)? {
332			Some(v) => v.bytes,
333			None => return Ok(bytes),
334		};
335
336		let mut rows_buf = [EncodedRingBufferRow::from(bytes.clone()).thaw()];
337		let ids = [id];
338		RingBufferRowInterceptor::pre_update(self, &ringbuffer, &ids, &mut rows_buf)?;
339		let [bytes] = rows_buf;
340		let bytes = bytes.freeze_bytes();
341
342		if let Some(expected) = partition {
343			let shape = row_shape_from_columns(RowFamily::RingBuffer, &ringbuffer.columns);
344			let indices = partition_col_indices(&ringbuffer.columns, &ringbuffer.partition_by);
345			if Partition::of(&partition_values(&shape, &bytes, &indices)) != expected {
346				return Err(PartitionError::ImmutablePartitionColumn {
347					object: ObjectId::ringbuffer(ringbuffer.id),
348				}
349				.into());
350			}
351		}
352
353		if self.get_committed(&key)?.is_some() {
354			self.mark_preexisting(&key)?;
355		}
356		self.set(&key, bytes.clone())?;
357
358		let posts = [bytes.clone()];
359		let pres = [pre.clone()];
360		RingBufferRowInterceptor::post_update(self, &ringbuffer, &ids, &posts, &pres)?;
361
362		self.track_flow_change(build_ringbuffer_update_change(&ringbuffer, id, &pre, &bytes));
363
364		Ok(bytes)
365	}
366
367	fn remove_from_ringbuffer(
368		&mut self,
369		ringbuffer: &RingBuffer,
370		partition: Option<Partition>,
371		id: RowNumber,
372	) -> Result<EncodedBytes> {
373		let key = ringbuffer_key(ringbuffer, partition, id);
374
375		let displayed = match self.get(&key)? {
376			Some(v) => v.bytes,
377			None => return Ok(EncodedBytes(CowVec::new(vec![]))),
378		};
379		let committed = self.get_committed(&key)?.map(|v| v.bytes);
380
381		let ids = [id];
382		RingBufferRowInterceptor::pre_delete(self, ringbuffer, &ids)?;
383
384		let pre_for_cdc = committed.clone().unwrap_or_else(|| displayed.clone());
385
386		if committed.is_some() {
387			self.mark_preexisting(&key)?;
388		}
389		self.remove_with_pre(&key, pre_for_cdc.clone())?;
390
391		let pre_rows = [pre_for_cdc.clone()];
392		RingBufferRowInterceptor::post_delete(self, ringbuffer, &ids, &pre_rows)?;
393
394		self.track_flow_change(build_ringbuffer_remove_change(ringbuffer, id, &pre_for_cdc));
395
396		Ok(displayed)
397	}
398}
399
400impl RingBufferOperations for Transaction<'_> {
401	fn insert_ringbuffer(&mut self, _ringbuffer: RingBuffer, _row: EncodedBytes) -> Result<RowNumber> {
402		unimplemented!(
403			"Ring buffer insert must be called with explicit row_number through insert_ringbuffer_at"
404		)
405	}
406
407	fn insert_ringbuffer_at(
408		&mut self,
409		ringbuffer: &RingBuffer,
410		shape: &RowShape,
411		partition: Option<Partition>,
412		row_number: RowNumber,
413		bytes: EncodedBytes,
414	) -> Result<EncodedBytes> {
415		match self {
416			Transaction::Command(txn) => {
417				txn.insert_ringbuffer_at(ringbuffer, shape, partition, row_number, bytes)
418			}
419			Transaction::Admin(txn) => {
420				txn.insert_ringbuffer_at(ringbuffer, shape, partition, row_number, bytes)
421			}
422			Transaction::Test(t) => {
423				t.inner.insert_ringbuffer_at(ringbuffer, shape, partition, row_number, bytes)
424			}
425			Transaction::Query(_) => panic!("Write operations not supported on Query transaction"),
426			Transaction::Replica(_) => panic!("Write operations not supported on Replica transaction"),
427		}
428	}
429
430	fn update_ringbuffer(
431		&mut self,
432		ringbuffer: RingBuffer,
433		partition: Option<Partition>,
434		id: RowNumber,
435		bytes: EncodedBytes,
436	) -> Result<EncodedBytes> {
437		match self {
438			Transaction::Command(txn) => txn.update_ringbuffer(ringbuffer, partition, id, bytes),
439			Transaction::Admin(txn) => txn.update_ringbuffer(ringbuffer, partition, id, bytes),
440			Transaction::Test(t) => t.inner.update_ringbuffer(ringbuffer, partition, id, bytes),
441			Transaction::Query(_) => panic!("Write operations not supported on Query transaction"),
442			Transaction::Replica(_) => panic!("Write operations not supported on Replica transaction"),
443		}
444	}
445
446	fn remove_from_ringbuffer(
447		&mut self,
448		ringbuffer: &RingBuffer,
449		partition: Option<Partition>,
450		id: RowNumber,
451	) -> Result<EncodedBytes> {
452		match self {
453			Transaction::Command(txn) => txn.remove_from_ringbuffer(ringbuffer, partition, id),
454			Transaction::Admin(txn) => txn.remove_from_ringbuffer(ringbuffer, partition, id),
455			Transaction::Test(t) => t.inner.remove_from_ringbuffer(ringbuffer, partition, id),
456			Transaction::Query(_) => panic!("Write operations not supported on Query transaction"),
457			Transaction::Replica(_) => panic!("Write operations not supported on Replica transaction"),
458		}
459	}
460}