Skip to main content

reifydb_engine/vm/volcano/scan/
ringbuffer.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::sync::Arc;
5
6use reifydb_codec::encoded::{row::EncodedRow, shape::RowShape};
7use reifydb_core::{
8	interface::{
9		catalog::{dictionary::Dictionary, ringbuffer::PartitionedMetadata, shape::ShapeId},
10		resolved::ResolvedRingBuffer,
11	},
12	internal_error,
13	key::{
14		EncodableKey,
15		partitioned_row::{PartitionedRowKey, RowLocator},
16		row::RowKey,
17	},
18	value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns, headers::ColumnHeaders},
19};
20use reifydb_transaction::{multi::RangeScope, transaction::Transaction};
21use reifydb_value::{
22	fragment::Fragment,
23	util::cowvec::CowVec,
24	value::{partition::Partition, row_number::RowNumber, value_type::ValueType},
25};
26use tracing::instrument;
27
28use super::super::decode_dictionary_columns;
29use crate::{
30	Result,
31	vm::volcano::query::{QueryContext, QueryNode},
32};
33
34pub struct RingBufferScan {
35	ringbuffer: ResolvedRingBuffer,
36
37	partitions: Vec<PartitionedMetadata>,
38	current_partition_index: usize,
39	headers: ColumnHeaders,
40	shape: Option<RowShape>,
41
42	storage_types: Vec<ValueType>,
43
44	dictionaries: Vec<Option<Dictionary>>,
45
46	partition_col_indices: Vec<usize>,
47	current_partition_rows: Vec<(RowNumber, EncodedRow)>,
48	current_partition_cursor: usize,
49	current_partition_loaded: bool,
50	finished: bool,
51	context: Option<Arc<QueryContext>>,
52	initialized: bool,
53}
54
55impl RingBufferScan {
56	pub fn new(
57		ringbuffer: ResolvedRingBuffer,
58		context: Arc<QueryContext>,
59		rx: &mut Transaction<'_>,
60	) -> Result<Self> {
61		let mut storage_types = Vec::with_capacity(ringbuffer.columns().len());
62		let mut dictionaries = Vec::with_capacity(ringbuffer.columns().len());
63
64		for col in ringbuffer.columns() {
65			if let Some(dict_id) = col.dictionary_id {
66				if let Some(dict) = context.services.catalog.find_dictionary(rx, dict_id)? {
67					storage_types.push(ValueType::DictionaryId);
68					dictionaries.push(Some(dict));
69				} else {
70					storage_types.push(col.constraint.get_type());
71					dictionaries.push(None);
72				}
73			} else {
74				storage_types.push(col.constraint.get_type());
75				dictionaries.push(None);
76			}
77		}
78
79		let partition_col_indices: Vec<usize> = ringbuffer
80			.def()
81			.partition_by
82			.iter()
83			.map(|pb_col| ringbuffer.columns().iter().position(|c| c.name == *pb_col).unwrap())
84			.collect();
85
86		let headers = ColumnHeaders {
87			columns: ringbuffer.columns().iter().map(|col| Fragment::internal(&col.name)).collect(),
88		};
89
90		Ok(Self {
91			ringbuffer,
92			partitions: Vec::new(),
93			current_partition_index: 0,
94			headers,
95			shape: None,
96			storage_types,
97			dictionaries,
98			partition_col_indices,
99			current_partition_rows: Vec::new(),
100			current_partition_cursor: 0,
101			current_partition_loaded: false,
102			finished: false,
103			context: Some(context),
104			initialized: false,
105		})
106	}
107
108	fn get_or_load_shape(&mut self, rx: &mut Transaction, first_row: &EncodedRow) -> Result<RowShape> {
109		if let Some(shape) = &self.shape {
110			return Ok(shape.clone());
111		}
112
113		let fingerprint = first_row.fingerprint();
114
115		let stored_ctx = self.context.as_ref().expect("RingBufferScan context not set");
116		let shape = stored_ctx.services.catalog.get_or_load_row_shape(fingerprint, rx)?.ok_or_else(|| {
117			internal_error!(
118				"RowShape with fingerprint {:?} not found for ringbuffer {}",
119				fingerprint,
120				self.ringbuffer.def().name
121			)
122		})?;
123
124		self.shape = Some(shape.clone());
125
126		Ok(shape)
127	}
128
129	fn load_partition_rows(
130		&self,
131		txn: &mut Transaction<'_>,
132		partition_index: usize,
133	) -> Result<Vec<(RowNumber, EncodedRow)>> {
134		let pm = &self.partitions[partition_index];
135		let rb_id = self.ringbuffer.def().id;
136
137		if self.partition_col_indices.is_empty() {
138			let mut out = Vec::new();
139			for rn_value in pm.metadata.head..pm.metadata.tail {
140				let rn = RowNumber(rn_value);
141				if let Some(multi) = txn.get(&RowKey::encoded(rb_id, rn))? {
142					out.push((rn, multi.row));
143				}
144			}
145			return Ok(out);
146		}
147
148		let hash = Partition::of(&pm.partition_values);
149		let mut out = Vec::new();
150		let mut last_key = None;
151		loop {
152			let batch: Vec<_> = txn
153				.range(
154					PartitionedRowKey::partition_scan_range(
155						ShapeId::ringbuffer(rb_id),
156						hash,
157						last_key.as_ref(),
158					),
159					RangeScope::All,
160					1024,
161				)?
162				.collect::<Result<Vec<_>>>()?;
163			if batch.is_empty() {
164				break;
165			}
166			let n = batch.len();
167			for entry in batch {
168				if let Some(RowLocator::Row(rn)) =
169					PartitionedRowKey::decode(&entry.key).map(|pk| pk.locator)
170				{
171					out.push((rn, entry.row));
172				}
173				last_key = Some(entry.key);
174			}
175			if n < 1024 {
176				break;
177			}
178		}
179		out.sort_by_key(|(rn, _)| rn.0);
180		Ok(out)
181	}
182}
183
184impl QueryNode for RingBufferScan {
185	#[instrument(name = "volcano::scan::ringbuffer::initialize", level = "trace", skip_all)]
186	fn initialize<'a>(&mut self, txn: &mut Transaction<'a>, ctx: &QueryContext) -> Result<()> {
187		if !self.initialized {
188			self.partitions =
189				ctx.services.catalog.list_ringbuffer_partitions(txn, self.ringbuffer.def())?;
190			self.initialized = true;
191		}
192		Ok(())
193	}
194
195	#[instrument(name = "volcano::scan::ringbuffer::next", level = "trace", skip_all)]
196	fn next<'a>(&mut self, txn: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<Columns>> {
197		if self.finished {
198			return Ok(None);
199		}
200
201		let batch_size = self.context.as_ref().expect("RingBufferScan context not set").batch_size as usize;
202		let partitioned = !self.partition_col_indices.is_empty();
203
204		let mut batch_rows: Vec<EncodedRow> = Vec::new();
205		let mut row_numbers: Vec<RowNumber> = Vec::new();
206		let mut partitions_sidecar: Vec<Partition> = Vec::new();
207
208		while batch_rows.len() < batch_size && self.current_partition_index < self.partitions.len() {
209			if !self.current_partition_loaded {
210				self.current_partition_rows =
211					self.load_partition_rows(txn, self.current_partition_index)?;
212				self.current_partition_cursor = 0;
213				self.current_partition_loaded = true;
214			}
215
216			let hash = if partitioned {
217				Some(Partition::of(&self.partitions[self.current_partition_index].partition_values))
218			} else {
219				None
220			};
221
222			while batch_rows.len() < batch_size
223				&& self.current_partition_cursor < self.current_partition_rows.len()
224			{
225				let (rn, row) = self.current_partition_rows[self.current_partition_cursor].clone();
226				batch_rows.push(row);
227				row_numbers.push(rn);
228				if let Some(h) = hash {
229					partitions_sidecar.push(h);
230				}
231				self.current_partition_cursor += 1;
232			}
233
234			if self.current_partition_cursor >= self.current_partition_rows.len() {
235				self.current_partition_index += 1;
236				self.current_partition_loaded = false;
237			}
238		}
239
240		if !batch_rows.is_empty() {
241			let storage_columns: Vec<ColumnWithName> = self
242				.ringbuffer
243				.columns()
244				.iter()
245				.enumerate()
246				.map(|(idx, col)| ColumnWithName {
247					name: Fragment::internal(&col.name),
248					data: ColumnBuffer::with_capacity(self.storage_types[idx].clone(), 0),
249				})
250				.collect();
251
252			let mut columns =
253				Columns::with_system_columns(storage_columns, Vec::new(), Vec::new(), Vec::new());
254			let shape = self.get_or_load_shape(txn, &batch_rows[0])?;
255			columns.append_rows(&shape, batch_rows.into_iter(), row_numbers.clone())?;
256			columns.row_numbers = CowVec::new(row_numbers);
257			if partitioned {
258				columns.partitions = CowVec::new(partitions_sidecar);
259			}
260
261			decode_dictionary_columns(&mut columns, &self.dictionaries, txn)?;
262
263			return Ok(Some(columns));
264		}
265
266		self.finished = true;
267		if self.partitions.is_empty() || self.partitions.iter().all(|p| p.metadata.is_empty()) {
268			let columns: Vec<ColumnWithName> = self
269				.ringbuffer
270				.columns()
271				.iter()
272				.map(|col| ColumnWithName {
273					name: Fragment::internal(&col.name),
274					data: ColumnBuffer::none_typed(col.constraint.get_type(), 0),
275				})
276				.collect();
277			return Ok(Some(Columns::new(columns)));
278		}
279		Ok(None)
280	}
281
282	fn headers(&self) -> Option<ColumnHeaders> {
283		Some(self.headers.clone())
284	}
285}