reifydb_engine/vm/volcano/scan/
ringbuffer.rs1use 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}