reifydb_engine/vm/volcano/scan/
table.rs1use std::{ops::Bound, sync::Arc};
5
6use reifydb_codec::row::{bytes::EncodedBytes, shape::RowShape, table::EncodedTableRow};
7use reifydb_core::{
8 common::CommitVersion,
9 error::diagnostic,
10 interface::{
11 catalog::{dictionary::Dictionary, storage::StorageId},
12 resolved::ResolvedTable,
13 store::MultiVersionRow,
14 },
15 key::row::{StoragePartitionedRowKey, StorageRowKey},
16 value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns, headers::ColumnHeaders},
17};
18use reifydb_transaction::{multi::RangeScope, transaction::Transaction};
19use reifydb_value::{
20 error,
21 fragment::Fragment,
22 reifydb_assertions,
23 value::{partition::Partition, row_number::RowNumber, system_columns::SystemColumns, value_type::ValueType},
24};
25use tracing::instrument;
26
27use super::super::decode_dictionary_columns;
28use crate::{
29 Result,
30 vm::volcano::query::{QueryContext, QueryNode},
31};
32
33pub struct TableScanNode {
34 table: ResolvedTable,
35 context: Option<Arc<QueryContext>>,
36 headers: ColumnHeaders,
37
38 storage_types: Vec<ValueType>,
39
40 dictionaries: Vec<Option<Dictionary>>,
41
42 shape: Option<RowShape>,
43 resume: Resume,
44 exhausted: bool,
45
46 partition: Option<Partition>,
47
48 min_commit_version: Option<CommitVersion>,
49}
50
51impl TableScanNode {
52 pub fn with_min_commit_version(mut self, min_commit_version: Option<CommitVersion>) -> Self {
53 self.min_commit_version = min_commit_version;
54 self
55 }
56
57 pub fn new(
58 table: ResolvedTable,
59 partition: Option<Partition>,
60 context: Arc<QueryContext>,
61 rx: &mut Transaction<'_>,
62 ) -> Result<Self> {
63 let mut storage_types = Vec::with_capacity(table.columns().len());
64 let mut dictionaries = Vec::with_capacity(table.columns().len());
65
66 for col in table.columns() {
67 if let Some(dict_id) = col.dictionary_id {
68 if let Some(dict) = context.services.catalog.find_dictionary(rx, dict_id)? {
69 storage_types.push(ValueType::DictionaryId);
70 dictionaries.push(Some(dict));
71 } else {
72 storage_types.push(col.constraint.get_type());
73 dictionaries.push(None);
74 }
75 } else {
76 storage_types.push(col.constraint.get_type());
77 dictionaries.push(None);
78 }
79 }
80
81 let headers = ColumnHeaders {
82 columns: table.columns().iter().map(|col| Fragment::internal(&col.name)).collect(),
83 };
84
85 let resume = if table.def().partition_by.is_empty() {
86 Resume::Row(None)
87 } else {
88 Resume::Partitioned(None)
89 };
90
91 Ok(Self {
92 table,
93 context: Some(context),
94 headers,
95 storage_types,
96 dictionaries,
97 shape: None,
98 resume,
99 exhausted: false,
100 partition,
101 min_commit_version: None,
102 })
103 }
104
105 fn get_or_load_shape<'a>(&mut self, rx: &mut Transaction<'a>, first: &EncodedBytes) -> Result<RowShape> {
106 if let Some(shape) = &self.shape {
107 return Ok(shape.clone());
108 }
109
110 let fingerprint = EncodedTableRow::view(first).fingerprint();
111
112 let stored_ctx = self.context.as_ref().expect("TableScanNode context not set");
113 let shape = stored_ctx.services.catalog.get_or_load_row_shape(fingerprint, rx)?.ok_or_else(|| {
114 error!(diagnostic::internal::internal(format!(
115 "RowShape with fingerprint {:?} not found for table {}",
116 fingerprint,
117 self.table.def().name
118 )))
119 })?;
120
121 self.shape = Some(shape.clone());
122
123 Ok(shape)
124 }
125
126 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::drain_partitioned")]
127 fn drain_batch_partitioned(
128 stream: &mut dyn Iterator<Item = Result<MultiVersionRow<StoragePartitionedRowKey>>>,
129 batch_size: u64,
130 ) -> Result<(ScannedBatch, Option<StoragePartitionedRowKey>)> {
131 let mut batch = ScannedBatch::default();
132 let mut last = None;
133
134 for _ in 0..batch_size {
135 match stream.next() {
136 Some(Ok(multi)) => {
137 batch.rows.push(multi.bytes);
138 batch.row_numbers.push(multi.key.row());
139 batch.partitions.push(multi.key.partition());
140 last = Some(multi.key);
141 }
142 Some(Err(e)) => return Err(e),
143 None => {
144 batch.exhausted = true;
145 break;
146 }
147 }
148 }
149
150 Ok((batch, last))
151 }
152
153 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::drain_row")]
154 fn drain_batch_row(
155 stream: &mut dyn Iterator<Item = Result<MultiVersionRow<StorageRowKey>>>,
156 batch_size: u64,
157 ) -> Result<(ScannedBatch, Option<StorageRowKey>)> {
158 let mut batch = ScannedBatch::default();
159 let mut last = None;
160
161 for _ in 0..batch_size {
162 match stream.next() {
163 Some(Ok(multi)) => {
164 batch.rows.push(multi.bytes);
165 batch.row_numbers.push(multi.key.row());
166 last = Some(multi.key);
167 }
168 Some(Err(e)) => return Err(e),
169 None => {
170 batch.exhausted = true;
171 break;
172 }
173 }
174 }
175
176 Ok((batch, last))
177 }
178
179 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::column_alloc")]
180 fn storage_columns(&self) -> Vec<ColumnWithName> {
181 self.table
182 .columns()
183 .iter()
184 .enumerate()
185 .map(|(idx, col)| ColumnWithName {
186 name: Fragment::internal(&col.name),
187 data: ColumnBuffer::with_capacity(self.storage_types[idx].clone(), 0),
188 })
189 .collect()
190 }
191
192 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::empty_columns")]
193 fn empty_columns(&self) -> Vec<ColumnWithName> {
194 self.table
195 .columns()
196 .iter()
197 .map(|col| ColumnWithName {
198 name: Fragment::internal(&col.name),
199 data: ColumnBuffer::none_typed(col.constraint.get_type(), 0),
200 })
201 .collect()
202 }
203
204 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::append_rows")]
205 fn append_batch<'a>(
206 &mut self,
207 rx: &mut Transaction<'a>,
208 columns: &mut Columns,
209 bytes_vec: Vec<EncodedBytes>,
210 row_numbers: Vec<RowNumber>,
211 ) -> Result<()> {
212 let shape = self.get_or_load_shape(rx, &bytes_vec[0])?;
213 columns.append_rows(&shape, bytes_vec.into_iter(), row_numbers)?;
214 Ok(())
215 }
216}
217
218#[derive(Clone, Copy)]
219enum Resume {
220 Row(Option<StorageRowKey>),
221 Partitioned(Option<StoragePartitionedRowKey>),
222}
223
224#[derive(Default)]
225struct ScannedBatch {
226 rows: Vec<EncodedBytes>,
227 row_numbers: Vec<RowNumber>,
228 partitions: Vec<Partition>,
229 exhausted: bool,
230}
231
232fn partitioned_bounds(
233 partition: Option<Partition>,
234 last: Option<StoragePartitionedRowKey>,
235) -> (Bound<StoragePartitionedRowKey>, Bound<StoragePartitionedRowKey>) {
236 let start = match (last, partition) {
237 (Some(key), _) => Bound::Excluded(key),
238 (None, Some(partition)) => {
239 Bound::Included(StoragePartitionedRowKey::new(partition, RowNumber(u64::MAX)))
240 }
241 (None, None) => Bound::Unbounded,
242 };
243 let end = match partition {
244 Some(partition) => Bound::Included(StoragePartitionedRowKey::new(partition, RowNumber(u64::MIN))),
245 None => Bound::Unbounded,
246 };
247 (start, end)
248}
249
250impl QueryNode for TableScanNode {
251 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::initialize")]
252 fn initialize<'a>(&mut self, _rx: &mut Transaction<'a>, _ctx: &QueryContext) -> Result<()> {
253 Ok(())
254 }
255
256 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::next")]
257 fn next<'a>(&mut self, rx: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<Columns>> {
258 reifydb_assertions! {
259 assert!(self.context.is_some(), "TableScanNode::next() called before initialize()");
260 }
261 let stored_ctx = self.context.as_ref().unwrap();
262
263 if self.exhausted {
264 return Ok(None);
265 }
266
267 let batch_size = stored_ctx.batch_size;
268
269 let scope = match self.min_commit_version {
270 Some(v) => RangeScope::After(v),
271 None => RangeScope::All,
272 };
273
274 let storage: StorageId = self.table.def().id.into();
275
276 let (scanned, next_resume, resumed) = match self.resume {
277 Resume::Partitioned(last) => {
278 let (start, end) = partitioned_bounds(self.partition, last);
279 let (scanned, new_last) = {
280 let mut stream = rx.range_partitioned_row(
281 storage,
282 start,
283 end,
284 scope,
285 batch_size as usize,
286 )?;
287 Self::drain_batch_partitioned(&mut stream, batch_size)?
288 };
289 (scanned, Resume::Partitioned(new_last), last.is_some())
290 }
291 Resume::Row(last) => {
292 let start = match last {
293 Some(key) => Bound::Excluded(key),
294 None => Bound::Unbounded,
295 };
296 let (scanned, new_last) = {
297 let mut stream = rx.range_row(
298 storage,
299 start,
300 Bound::Unbounded,
301 scope,
302 batch_size as usize,
303 )?;
304 Self::drain_batch_row(&mut stream, batch_size)?
305 };
306 (scanned, Resume::Row(new_last), last.is_some())
307 }
308 };
309
310 if scanned.exhausted {
311 self.exhausted = true;
312 }
313
314 if scanned.rows.is_empty() {
315 self.exhausted = true;
316 if !resumed {
317 return Ok(Some(Columns::new(self.empty_columns())));
318 }
319 return Ok(None);
320 }
321
322 self.resume = next_resume;
323
324 let mut columns = Columns::with_system(self.storage_columns(), SystemColumns::default());
325 self.append_batch(rx, &mut columns, scanned.rows, scanned.row_numbers)?;
326
327 if !scanned.partitions.is_empty() {
328 columns.system.set_partitions(scanned.partitions);
329 }
330
331 decode_dictionary_columns(&mut columns, &self.dictionaries, rx)?;
332
333 Ok(Some(columns))
334 }
335
336 fn headers(&self) -> Option<ColumnHeaders> {
337 Some(self.headers.clone())
338 }
339}