1use std::{ops::Bound, sync::Arc};
5
6use arrow_array::{ArrayRef, RecordBatch};
7use arrow_schema::FieldRef;
8use reifydb_codec::row::{bytes::EncodedBytes, ringbuffer::EncodedRingBufferRow, shape::RowShape};
9use reifydb_core::{
10 common::TimeSource,
11 interface::{
12 catalog::{dictionary::Dictionary, ringbuffer::PartitionedMetadata},
13 resolved::ResolvedRingBuffer,
14 store::MultiVersionRow,
15 },
16 internal_error,
17 key::{
18 any::TaggedKey,
19 row::{PartitionedRowKey, StorageRowKey},
20 },
21 partition::partition_col_indices,
22 value::{
23 batch::{append_rows, batch},
24 column::{builder::ColumnBuilder, headers::ColumnHeaders},
25 },
26};
27use reifydb_transaction::{multi::RangeScope, transaction::Transaction};
28use reifydb_value::value::{
29 partition::Partition,
30 row_number::RowNumber,
31 system_columns::{SystemColumn, with_system_column},
32 value_type::ValueType,
33};
34use tracing::instrument;
35
36use super::{
37 super::decode_dictionary_columns,
38 empty_scan,
39 merge::{MergeLayout, PartitionMerge},
40 partition_array, scan_headers, source_system_columns, storage_partitioned_row,
41};
42use crate::{
43 Result,
44 vm::volcano::query::{QueryContext, QueryNode},
45};
46
47pub struct RingBufferScan {
48 ringbuffer: ResolvedRingBuffer,
49
50 partitions: Vec<PartitionedMetadata>,
51 current_partition_index: usize,
52 headers: ColumnHeaders,
53 shape: Option<RowShape>,
54
55 storage_types: Vec<ValueType>,
56
57 dictionaries: Vec<Option<(Dictionary, ValueType)>>,
58
59 partition_col_indices: Vec<usize>,
60 current_partition_rows: Vec<(RowNumber, EncodedBytes)>,
61 current_partition_cursor: usize,
62 current_partition_loaded: bool,
63 finished: bool,
64 context: Option<Arc<QueryContext>>,
65 initialized: bool,
66 system_columns: Vec<SystemColumn>,
67 merge: Option<PartitionMerge>,
68 keep_dictionary_ids: bool,
69}
70
71impl RingBufferScan {
72 pub fn new(
73 ringbuffer: ResolvedRingBuffer,
74 context: Arc<QueryContext>,
75 rx: &mut Transaction<'_>,
76 ) -> Result<Self> {
77 let mut storage_types = Vec::with_capacity(ringbuffer.columns().len());
78 let mut dictionaries = Vec::with_capacity(ringbuffer.columns().len());
79
80 for col in ringbuffer.columns() {
81 if let Some(dict_id) = col.dictionary_id {
82 if let Some(dict) = context.services.catalog.find_dictionary(rx, dict_id)? {
83 storage_types.push(ValueType::DictionaryId);
84 dictionaries.push(Some((dict, col.constraint.get_type())));
85 } else {
86 storage_types.push(col.constraint.get_type());
87 dictionaries.push(None);
88 }
89 } else {
90 storage_types.push(col.constraint.get_type());
91 dictionaries.push(None);
92 }
93 }
94
95 let partition_col_indices = partition_col_indices(ringbuffer.columns(), &ringbuffer.def().partition_by);
96
97 let system_columns = source_system_columns(
98 !partition_col_indices.is_empty(),
99 ringbuffer.def().time != TimeSource::None,
100 false,
101 );
102 let headers = scan_headers(ringbuffer.columns().iter().map(|col| col.name.as_str()), &system_columns);
103
104 Ok(Self {
105 ringbuffer,
106 partitions: Vec::new(),
107 current_partition_index: 0,
108 headers,
109 shape: None,
110 storage_types,
111 dictionaries,
112 partition_col_indices,
113 current_partition_rows: Vec::new(),
114 current_partition_cursor: 0,
115 current_partition_loaded: false,
116 finished: false,
117 context: Some(context),
118 initialized: false,
119 system_columns,
120 merge: None,
121 keep_dictionary_ids: false,
122 })
123 }
124
125 pub(crate) fn oldest_first(mut self) -> Self {
126 if !self.partition_col_indices.is_empty() {
127 self.merge = Some(PartitionMerge::new(MergeLayout::Row, self.ringbuffer.def().id.into(), None));
128 }
129 self
130 }
131
132 pub(crate) fn keep_dictionary_ids(mut self) -> Self {
133 self.keep_dictionary_ids = true;
134 self
135 }
136
137 fn get_or_load_shape(&mut self, rx: &mut Transaction, first: &EncodedBytes) -> Result<RowShape> {
138 if let Some(shape) = &self.shape {
139 return Ok(shape.clone());
140 }
141
142 let fingerprint = EncodedRingBufferRow::view(first).fingerprint();
143
144 let stored_ctx = self.context.as_ref().expect("RingBufferScan context not set");
145 let shape = stored_ctx.services.catalog.get_or_load_row_shape(fingerprint, rx)?.ok_or_else(|| {
146 internal_error!(
147 "RowShape with fingerprint {:?} not found for ringbuffer {}",
148 fingerprint,
149 self.ringbuffer.def().name
150 )
151 })?;
152
153 self.shape = Some(shape.clone());
154
155 Ok(shape)
156 }
157
158 #[instrument(level = "trace", skip_all, name = "volcano::scan::ringbuffer::drain")]
159 fn drain_batch(
160 &mut self,
161 txn: &mut Transaction<'_>,
162 batch_size: usize,
163 partitioned: bool,
164 ) -> Result<(Vec<EncodedBytes>, Vec<RowNumber>, Vec<Partition>)> {
165 let mut batch: Vec<EncodedBytes> = Vec::new();
166 let mut row_numbers: Vec<RowNumber> = Vec::new();
167 let mut partitions_sidecar: Vec<Partition> = Vec::new();
168
169 while batch.len() < batch_size && self.current_partition_index < self.partitions.len() {
170 if !self.current_partition_loaded {
171 self.current_partition_rows =
172 self.load_partition_rows(txn, self.current_partition_index)?;
173 self.current_partition_cursor = 0;
174 self.current_partition_loaded = true;
175 }
176
177 #[allow(clippy::disallowed_methods)]
178 let hash = if partitioned {
179 Some(Partition::of(&self.partitions[self.current_partition_index].partition_values))
180 } else {
181 None
182 };
183
184 while batch.len() < batch_size
185 && self.current_partition_cursor < self.current_partition_rows.len()
186 {
187 let (rn, row) = self.current_partition_rows[self.current_partition_cursor].clone();
188 batch.push(row);
189 row_numbers.push(rn);
190 if let Some(h) = hash {
191 partitions_sidecar.push(h);
192 }
193 self.current_partition_cursor += 1;
194 }
195
196 if self.current_partition_cursor >= self.current_partition_rows.len() {
197 self.current_partition_index += 1;
198 self.current_partition_loaded = false;
199 }
200 }
201
202 Ok((batch, row_numbers, partitions_sidecar))
203 }
204
205 #[instrument(level = "trace", skip_all, name = "volcano::scan::ringbuffer::column_alloc")]
206 fn storage_columns(&self) -> Vec<(FieldRef, ArrayRef)> {
207 self.ringbuffer
208 .columns()
209 .iter()
210 .enumerate()
211 .map(|(idx, col)| {
212 ColumnBuilder::with_capacity(self.storage_types[idx].clone(), 0).finish(&col.name)
213 })
214 .collect()
215 }
216
217 #[instrument(level = "trace", skip_all, name = "volcano::scan::ringbuffer::empty_columns")]
218 fn empty_columns(&self) -> Vec<(FieldRef, ArrayRef)> {
219 self.ringbuffer
220 .columns()
221 .iter()
222 .map(|col| ColumnBuilder::with_capacity(col.constraint.get_type(), 0).finish(&col.name))
223 .collect()
224 }
225
226 #[instrument(level = "trace", skip_all, name = "volcano::scan::ringbuffer::append_rows")]
227 fn append_batch(
228 &mut self,
229 txn: &mut Transaction<'_>,
230 columns: RecordBatch,
231 bytes_vec: Vec<EncodedBytes>,
232 row_numbers: Vec<RowNumber>,
233 ) -> Result<RecordBatch> {
234 let shape = self.get_or_load_shape(txn, &bytes_vec[0])?;
235 append_rows(columns, &shape, bytes_vec, row_numbers)
236 }
237
238 #[instrument(level = "trace", skip_all, name = "volcano::scan::ringbuffer::load_partition")]
239 fn load_partition_rows(
240 &self,
241 txn: &mut Transaction<'_>,
242 partition_index: usize,
243 ) -> Result<Vec<(RowNumber, EncodedBytes)>> {
244 let pm = &self.partitions[partition_index];
245 let rb_id = self.ringbuffer.def().id;
246
247 if self.partition_col_indices.is_empty() {
248 if pm.metadata.head >= pm.metadata.tail {
249 return Ok(Vec::new());
250 }
251 let mut out = txn
252 .range_row(
253 rb_id.into(),
254 Bound::Included(StorageRowKey::new(RowNumber(pm.metadata.tail - 1))),
255 Bound::Included(StorageRowKey::new(RowNumber(pm.metadata.head))),
256 RangeScope::All,
257 1024,
258 )?
259 .map(|entry| entry.map(|entry| (entry.key.row(), entry.bytes)))
260 .collect::<Result<Vec<_>>>()?;
261 out.sort_by_key(|(rn, _)| rn.0);
262 return Ok(out);
263 }
264
265 #[allow(clippy::disallowed_methods)]
266 let hash = Partition::of(&pm.partition_values);
267 let mut out = Vec::new();
268 let mut last_key = None;
269 loop {
270 let batch: Vec<_> = txn
271 .range(
272 PartitionedRowKey::partition_scan_range(rb_id, hash, last_key.as_ref()),
273 RangeScope::All,
274 1024,
275 )?
276 .collect::<Result<Vec<_>>>()?;
277 if batch.is_empty() {
278 break;
279 }
280 let n = batch.len();
281 for entry in batch {
282 if let TaggedKey::PartitionedRow(pk) = &entry.key {
283 out.push((pk.row, entry.bytes));
284 }
285 last_key = Some(entry.key.clone());
286 }
287 if n < 1024 {
288 break;
289 }
290 }
291 out.sort_by_key(|(rn, _)| rn.0);
292 Ok(out)
293 }
294}
295
296impl QueryNode for RingBufferScan {
297 #[instrument(name = "volcano::scan::ringbuffer::initialize", level = "trace", skip_all)]
298 fn initialize<'a>(&mut self, txn: &mut Transaction<'a>, ctx: &QueryContext) -> Result<()> {
299 if !self.initialized {
300 self.partitions =
301 ctx.services.catalog.list_ringbuffer_partitions(txn, self.ringbuffer.def())?;
302 self.initialized = true;
303 }
304 Ok(())
305 }
306
307 #[instrument(name = "volcano::scan::ringbuffer::next", level = "trace", skip_all)]
308 fn next<'a>(&mut self, txn: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<RecordBatch>> {
309 if self.finished {
310 return Ok(None);
311 }
312
313 let batch_size = self.context.as_ref().expect("RingBufferScan context not set").batch_size as usize;
314 let partitioned = !self.partition_col_indices.is_empty();
315
316 let (batch_rows, row_numbers, partitions_sidecar) = match self.merge.as_mut() {
317 Some(merge) => merged_batch(merge.next(txn, batch_size as u64)?)?,
318 None => self.drain_batch(txn, batch_size, partitioned)?,
319 };
320
321 if !batch_rows.is_empty() {
322 let columns = batch(self.storage_columns())?;
323 let mut columns = self.append_batch(txn, columns, batch_rows, row_numbers)?;
324 if partitioned {
325 columns = with_system_column(
326 columns,
327 SystemColumn::Partitions,
328 partition_array(&partitions_sidecar),
329 )?;
330 }
331
332 if self.keep_dictionary_ids {
333 return Ok(Some(columns));
334 }
335 return Ok(Some(decode_dictionary_columns(columns, &self.dictionaries, txn)?));
336 }
337
338 self.finished = true;
339 if self.partitions.is_empty() || self.partitions.iter().all(|p| p.metadata.is_empty()) {
340 return Ok(Some(empty_scan(self.empty_columns(), &self.system_columns)?));
341 }
342 Ok(None)
343 }
344
345 fn headers(&self) -> Option<ColumnHeaders> {
346 Some(self.headers.clone())
347 }
348}
349
350fn merged_batch(rows: Vec<MultiVersionRow<TaggedKey>>) -> Result<(Vec<EncodedBytes>, Vec<RowNumber>, Vec<Partition>)> {
351 let mut batch = Vec::with_capacity(rows.len());
352 let mut row_numbers = Vec::with_capacity(rows.len());
353 let mut partitions = Vec::with_capacity(rows.len());
354 for row in rows {
355 let row = storage_partitioned_row(row)?;
356 batch.push(row.bytes);
357 row_numbers.push(row.key.row());
358 partitions.push(row.key.partition());
359 }
360 Ok((batch, row_numbers, partitions))
361}