1use std::{collections::VecDeque, ops::Bound, sync::Arc};
5
6use arrow_array::{ArrayRef, RecordBatch, UInt64Array};
7use arrow_schema::FieldRef;
8use reifydb_codec::row::{
9 bytes::EncodedBytes,
10 shape::{RowShape, fingerprint::RowShapeFingerprint},
11 table::EncodedTableRow,
12};
13use reifydb_core::{
14 common::TimeSource,
15 error::diagnostic,
16 interface::{
17 catalog::{dictionary::Dictionary, storage::StorageId},
18 resolved::ResolvedTable,
19 store::MultiVersionRow,
20 },
21 key::{
22 any::TaggedKey,
23 row::{PartitionedRowKey, RowKeyRange, StoragePartitionedRowKey, StorageRowKey},
24 },
25 value::{
26 batch::{append_rows_fitted, batch},
27 column::{builder::ColumnBuilder, headers::ColumnHeaders},
28 },
29};
30use reifydb_transaction::{multi::RangeScope, transaction::Transaction};
31use reifydb_value::{
32 error, reifydb_assertions,
33 value::{
34 partition::Partition,
35 row_number::RowNumber,
36 system_columns::{SystemColumn, stamp_system_columns},
37 value_type::ValueType,
38 },
39};
40use tracing::instrument;
41
42use super::{
43 super::decode_dictionary_columns,
44 empty_scan,
45 merge::{MergeLayout, PartitionMerge},
46 partition_array, scan_headers, source_system_columns, storage_partitioned_row, storage_row,
47};
48use crate::{
49 Result,
50 vm::volcano::query::{QueryContext, QueryNode},
51};
52
53pub struct TableScanNode {
54 table: ResolvedTable,
55 context: Option<Arc<QueryContext>>,
56 headers: ColumnHeaders,
57
58 storage_types: Vec<ValueType>,
59
60 dictionaries: Vec<Option<(Dictionary, ValueType)>>,
61
62 shape: Option<RowShape>,
63 resume: Resume,
64 exhausted: bool,
65
66 partition: Option<Partition>,
67
68 pending: Option<VecDeque<Partition>>,
69
70 advanced: bool,
71
72 system_columns: Vec<SystemColumn>,
73
74 oldest_first: bool,
75 keep_dictionary_ids: bool,
76
77 merge: Option<PartitionMerge>,
78
79 storage_batch: Option<RecordBatch>,
80}
81
82impl TableScanNode {
83 pub fn new(
84 table: ResolvedTable,
85 partition: Option<Partition>,
86 context: Arc<QueryContext>,
87 rx: &mut Transaction<'_>,
88 ) -> Result<Self> {
89 let mut storage_types = Vec::with_capacity(table.columns().len());
90 let mut dictionaries = Vec::with_capacity(table.columns().len());
91
92 for col in table.columns() {
93 if let Some(dict_id) = col.dictionary_id {
94 if let Some(dict) = context.services.catalog.find_dictionary(rx, dict_id)? {
95 storage_types.push(ValueType::DictionaryId);
96 dictionaries.push(Some((dict, col.constraint.get_type())));
97 } else {
98 storage_types.push(col.constraint.get_type());
99 dictionaries.push(None);
100 }
101 } else {
102 storage_types.push(col.constraint.get_type());
103 dictionaries.push(None);
104 }
105 }
106
107 let system_columns = source_system_columns(
108 !table.def().partition_by.is_empty(),
109 table.def().time != TimeSource::None,
110 true,
111 );
112 let headers = scan_headers(table.columns().iter().map(|col| col.name.as_str()), &system_columns);
113
114 let resume = if table.def().partition_by.is_empty() {
115 Resume::Row(None)
116 } else {
117 Resume::Partitioned(None)
118 };
119
120 Ok(Self {
121 table,
122 context: Some(context),
123 headers,
124 storage_types,
125 dictionaries,
126 shape: None,
127 resume,
128 exhausted: false,
129 partition,
130 pending: None,
131 advanced: false,
132 system_columns,
133 oldest_first: false,
134 keep_dictionary_ids: false,
135 merge: None,
136 storage_batch: None,
137 })
138 }
139
140 pub fn with_partitions(
141 table: ResolvedTable,
142 partitions: Vec<Partition>,
143 context: Arc<QueryContext>,
144 rx: &mut Transaction<'_>,
145 ) -> Result<Self> {
146 let mut pending = VecDeque::from(partitions);
147 let first = pending.pop_front();
148 let mut node = Self::new(table, first, context, rx)?;
149 node.pending = Some(pending);
150 Ok(node)
151 }
152
153 fn scan_partition_list<'a>(
154 &mut self,
155 rx: &mut Transaction<'a>,
156 storage: StorageId,
157 scope: RangeScope,
158 batch_size: u64,
159 mut last: Option<StoragePartitionedRowKey>,
160 ) -> Result<(ScannedBatch, Option<StoragePartitionedRowKey>)> {
161 let mut batch = ScannedBatch::default();
162 loop {
163 let Some(partition) = self.partition else {
164 batch.exhausted = true;
165 return Ok((batch, None));
166 };
167 let room = batch_size - batch.rows.len() as u64;
168 let (start, end) = partitioned_bounds(Some(partition), last);
169 let (scanned, new_last) = {
170 let mut stream = rx.range_partitioned_row(storage, start, end, scope, room as usize)?;
171 Self::drain_batch_partitioned(&mut stream, room)?
172 };
173 let partition_done = scanned.exhausted;
174 batch.rows.extend(scanned.rows);
175 batch.row_numbers.extend(scanned.row_numbers);
176 batch.partitions.extend(scanned.partitions);
177 batch.commit_versions.extend(scanned.commit_versions);
178 if !partition_done {
179 return Ok((batch, new_last));
180 }
181 self.partition = self.pending.as_mut().and_then(VecDeque::pop_front);
182 self.advanced = true;
183 last = None;
184 if batch.rows.len() as u64 >= batch_size {
185 batch.exhausted = self.partition.is_none();
186 return Ok((batch, None));
187 }
188 }
189 }
190
191 pub(crate) fn oldest_first(mut self) -> Self {
192 self.oldest_first = true;
193 self
194 }
195
196 pub(crate) fn keep_dictionary_ids(mut self) -> Self {
197 self.keep_dictionary_ids = true;
198 self
199 }
200
201 fn get_or_load_shape<'a>(
202 &mut self,
203 rx: &mut Transaction<'a>,
204 fingerprint: RowShapeFingerprint,
205 ) -> Result<RowShape> {
206 if let Some(shape) = &self.shape
207 && shape.fingerprint() == fingerprint
208 {
209 return Ok(shape.clone());
210 }
211
212 let stored_ctx = self.context.as_ref().expect("TableScanNode context not set");
213 let shape = stored_ctx.services.catalog.get_or_load_row_shape(fingerprint, rx)?.ok_or_else(|| {
214 error!(diagnostic::internal::internal(format!(
215 "RowShape with fingerprint {:?} not found for table {}",
216 fingerprint,
217 self.table.def().name
218 )))
219 })?;
220
221 self.shape = Some(shape.clone());
222
223 Ok(shape)
224 }
225
226 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::drain_partitioned")]
227 fn drain_batch_partitioned(
228 stream: &mut dyn Iterator<Item = Result<MultiVersionRow<StoragePartitionedRowKey>>>,
229 batch_size: u64,
230 ) -> Result<(ScannedBatch, Option<StoragePartitionedRowKey>)> {
231 let mut batch = ScannedBatch::default();
232 let mut last = None;
233
234 for _ in 0..batch_size {
235 match stream.next() {
236 Some(Ok(multi)) => {
237 batch.rows.push(multi.bytes);
238 batch.row_numbers.push(multi.key.row());
239 batch.partitions.push(multi.key.partition());
240 batch.commit_versions.push(multi.version.0);
241 last = Some(multi.key);
242 }
243 Some(Err(e)) => return Err(e),
244 None => {
245 batch.exhausted = true;
246 break;
247 }
248 }
249 }
250
251 Ok((batch, last))
252 }
253
254 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::drain_row")]
255 fn drain_batch_row(
256 stream: &mut dyn Iterator<Item = Result<MultiVersionRow<StorageRowKey>>>,
257 batch_size: u64,
258 ) -> Result<(ScannedBatch, Option<StorageRowKey>)> {
259 let mut batch = ScannedBatch::default();
260 let mut last = None;
261
262 for _ in 0..batch_size {
263 match stream.next() {
264 Some(Ok(multi)) => {
265 batch.rows.push(multi.bytes);
266 batch.row_numbers.push(multi.key.row());
267 batch.commit_versions.push(multi.version.0);
268 last = Some(multi.key);
269 }
270 Some(Err(e)) => return Err(e),
271 None => {
272 batch.exhausted = true;
273 break;
274 }
275 }
276 }
277
278 Ok((batch, last))
279 }
280
281 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::column_alloc")]
282 fn storage_columns(&self) -> Vec<(FieldRef, ArrayRef)> {
283 self.table
284 .columns()
285 .iter()
286 .enumerate()
287 .map(|(idx, col)| {
288 ColumnBuilder::with_capacity(self.storage_types[idx].clone(), 0).finish(&col.name)
289 })
290 .collect()
291 }
292
293 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::empty_columns")]
294 fn empty_columns(&self) -> Vec<(FieldRef, ArrayRef)> {
295 self.table
296 .columns()
297 .iter()
298 .map(|col| ColumnBuilder::with_capacity(col.constraint.get_type(), 0).finish(&col.name))
299 .collect()
300 }
301
302 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::append_rows")]
303 fn append_batch<'a>(
304 &mut self,
305 rx: &mut Transaction<'a>,
306 columns: RecordBatch,
307 bytes_vec: Vec<EncodedBytes>,
308 row_numbers: Vec<RowNumber>,
309 ) -> Result<RecordBatch> {
310 let mut columns = columns;
311 let mut rows = bytes_vec.into_iter().peekable();
312 let mut numbers = row_numbers.into_iter();
313 while let Some(first) = rows.next() {
314 let fingerprint = EncodedTableRow::view(&first).fingerprint();
315 let mut run = vec![first];
316 while let Some(next) =
317 rows.next_if(|row| EncodedTableRow::view(row).fingerprint() == fingerprint)
318 {
319 run.push(next);
320 }
321 let run_numbers: Vec<RowNumber> = numbers.by_ref().take(run.len()).collect();
322 let shape = self.get_or_load_shape(rx, fingerprint)?;
323 columns = append_rows_fitted(columns, &shape, run, run_numbers)?;
324 }
325 Ok(columns)
326 }
327}
328
329#[derive(Clone, Copy)]
330enum Resume {
331 Row(Option<StorageRowKey>),
332 Partitioned(Option<StoragePartitionedRowKey>),
333}
334
335#[derive(Default)]
336struct ScannedBatch {
337 rows: Vec<EncodedBytes>,
338 row_numbers: Vec<RowNumber>,
339 partitions: Vec<Partition>,
340 commit_versions: Vec<u64>,
341 exhausted: bool,
342}
343
344fn partitioned_bounds(
345 partition: Option<Partition>,
346 last: Option<StoragePartitionedRowKey>,
347) -> (Bound<StoragePartitionedRowKey>, Bound<StoragePartitionedRowKey>) {
348 let start = match (last, partition) {
349 (Some(key), _) => Bound::Excluded(key),
350 (None, Some(partition)) => {
351 Bound::Included(StoragePartitionedRowKey::new(partition, RowNumber(u64::MAX)))
352 }
353 (None, None) => Bound::Unbounded,
354 };
355 let end = match partition {
356 Some(partition) => Bound::Included(StoragePartitionedRowKey::new(partition, RowNumber(u64::MIN))),
357 None => Bound::Unbounded,
358 };
359 (start, end)
360}
361
362impl QueryNode for TableScanNode {
363 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::initialize")]
364 fn initialize<'a>(&mut self, _rx: &mut Transaction<'a>, _ctx: &QueryContext) -> Result<()> {
365 Ok(())
366 }
367
368 #[instrument(level = "trace", skip_all, name = "volcano::scan::table::next")]
369 fn next<'a>(&mut self, rx: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<RecordBatch>> {
370 reifydb_assertions! {
371 assert!(self.context.is_some(), "TableScanNode::next() called before initialize()");
372 }
373 let stored_ctx = self.context.as_ref().unwrap();
374
375 if self.exhausted {
376 return Ok(None);
377 }
378
379 let batch_size = stored_ctx.batch_size;
380
381 let scope = RangeScope::All;
382
383 let storage: StorageId = self.table.def().id.into();
384
385 let (scanned, next_resume, resumed) = match self.resume {
386 Resume::Partitioned(last) if self.pending.is_some() => {
387 let resumed = last.is_some() || self.advanced;
388 let (scanned, new_last) =
389 self.scan_partition_list(rx, storage, scope, batch_size, last)?;
390 (scanned, Resume::Partitioned(new_last), resumed)
391 }
392 Resume::Partitioned(last) if self.oldest_first => {
393 let partition = self.partition;
394 let merge = self.merge.get_or_insert_with(|| {
395 PartitionMerge::new(
396 MergeLayout::Row,
397 storage,
398 partition.map(|partition| {
399 PartitionedRowKey::partition_range(storage, partition)
400 }),
401 )
402 });
403 let rows = merge.next(rx, batch_size)?;
404 let (scanned, new_last) = Self::drain_batch_partitioned(
405 &mut rows.into_iter().map(storage_partitioned_row),
406 batch_size,
407 )?;
408 (scanned, Resume::Partitioned(new_last), last.is_some())
409 }
410 Resume::Partitioned(last) => {
411 let (start, end) = partitioned_bounds(self.partition, last);
412 let (scanned, new_last) = {
413 let mut stream = rx.range_partitioned_row(
414 storage,
415 start,
416 end,
417 scope,
418 batch_size as usize,
419 )?;
420 Self::drain_batch_partitioned(&mut stream, batch_size)?
421 };
422 (scanned, Resume::Partitioned(new_last), last.is_some())
423 }
424 Resume::Row(last) if self.oldest_first => {
425 let last_key = last.map(|key| TaggedKey::Row(key.with_storage(storage)));
426 let range = RowKeyRange::scan_range_rev(storage, last_key.as_ref());
427 let (scanned, new_last) = {
428 let mut stream = rx
429 .range_rev(range, scope, batch_size as usize)?
430 .map(|row| row.and_then(storage_row));
431 Self::drain_batch_row(&mut stream, batch_size)?
432 };
433 (scanned, Resume::Row(new_last), last.is_some())
434 }
435 Resume::Row(last) => {
436 let start = match last {
437 Some(key) => Bound::Excluded(key),
438 None => Bound::Unbounded,
439 };
440 let (scanned, new_last) = {
441 let mut stream = rx.range_row(
442 storage,
443 start,
444 Bound::Unbounded,
445 scope,
446 batch_size as usize,
447 )?;
448 Self::drain_batch_row(&mut stream, batch_size)?
449 };
450 (scanned, Resume::Row(new_last), last.is_some())
451 }
452 };
453
454 if scanned.exhausted {
455 self.exhausted = true;
456 }
457
458 if scanned.rows.is_empty() {
459 self.exhausted = true;
460 if !resumed {
461 return Ok(Some(empty_scan(self.empty_columns(), &self.system_columns)?));
462 }
463 return Ok(None);
464 }
465
466 self.resume = next_resume;
467
468 let columns = match &self.storage_batch {
469 Some(storage) => storage.clone(),
470 None => {
471 let storage = batch(self.storage_columns())?;
472 self.storage_batch = Some(storage.clone());
473 storage
474 }
475 };
476 let columns = self.append_batch(rx, columns, scanned.rows, scanned.row_numbers)?;
477
478 let mut stamps: Vec<(SystemColumn, ArrayRef)> = Vec::new();
479 if !scanned.partitions.is_empty() {
480 stamps.push((SystemColumn::Partitions, partition_array(&scanned.partitions)));
481 }
482 stamps.push((SystemColumn::CommitVersion, Arc::new(UInt64Array::from(scanned.commit_versions))));
483 let columns = stamp_system_columns(columns, stamps)?;
484
485 if self.keep_dictionary_ids {
486 return Ok(Some(columns));
487 }
488 Ok(Some(decode_dictionary_columns(columns, &self.dictionaries, rx)?))
489 }
490
491 fn headers(&self) -> Option<ColumnHeaders> {
492 Some(self.headers.clone())
493 }
494}