Skip to main content

reifydb_engine/vm/volcano/scan/
table.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::sync::Arc;
5
6use reifydb_core::{
7	common::CommitVersion,
8	encoded::{key::EncodedKey, row::EncodedRow, shape::RowShape},
9	error::diagnostic,
10	interface::{catalog::dictionary::Dictionary, resolved::ResolvedTable},
11	key::{
12		EncodableKey,
13		row::{RowKey, RowKeyRange},
14	},
15	value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns, headers::ColumnHeaders},
16};
17use reifydb_transaction::{multi::RangeScope, transaction::Transaction};
18use reifydb_value::{
19	error, fragment::Fragment, reifydb_assertions, util::cowvec::CowVec, value::value_type::ValueType,
20};
21use tracing::instrument;
22
23use super::super::decode_dictionary_columns;
24use crate::{
25	Result,
26	vm::volcano::query::{QueryContext, QueryNode},
27};
28
29pub struct TableScanNode {
30	table: ResolvedTable,
31	context: Option<Arc<QueryContext>>,
32	headers: ColumnHeaders,
33
34	storage_types: Vec<ValueType>,
35
36	dictionaries: Vec<Option<Dictionary>>,
37
38	shape: Option<RowShape>,
39	last_key: Option<EncodedKey>,
40	exhausted: bool,
41
42	min_commit_version: Option<CommitVersion>,
43}
44
45impl TableScanNode {
46	pub fn with_min_commit_version(mut self, min_commit_version: Option<CommitVersion>) -> Self {
47		self.min_commit_version = min_commit_version;
48		self
49	}
50
51	pub fn new(table: ResolvedTable, context: Arc<QueryContext>, rx: &mut Transaction<'_>) -> Result<Self> {
52		let mut storage_types = Vec::with_capacity(table.columns().len());
53		let mut dictionaries = Vec::with_capacity(table.columns().len());
54
55		for col in table.columns() {
56			if let Some(dict_id) = col.dictionary_id {
57				if let Some(dict) = context.services.catalog.find_dictionary(rx, dict_id)? {
58					storage_types.push(ValueType::DictionaryId);
59					dictionaries.push(Some(dict));
60				} else {
61					storage_types.push(col.constraint.get_type());
62					dictionaries.push(None);
63				}
64			} else {
65				storage_types.push(col.constraint.get_type());
66				dictionaries.push(None);
67			}
68		}
69
70		let headers = ColumnHeaders {
71			columns: table.columns().iter().map(|col| Fragment::internal(&col.name)).collect(),
72		};
73
74		Ok(Self {
75			table,
76			context: Some(context),
77			headers,
78			storage_types,
79			dictionaries,
80			shape: None,
81			last_key: None,
82			exhausted: false,
83			min_commit_version: None,
84		})
85	}
86
87	fn get_or_load_shape<'a>(&mut self, rx: &mut Transaction<'a>, first_row: &EncodedRow) -> Result<RowShape> {
88		if let Some(shape) = &self.shape {
89			return Ok(shape.clone());
90		}
91
92		let fingerprint = first_row.fingerprint();
93
94		let stored_ctx = self.context.as_ref().expect("TableScanNode context not set");
95		let shape = stored_ctx.services.catalog.get_or_load_row_shape(fingerprint, rx)?.ok_or_else(|| {
96			error!(diagnostic::internal::internal(format!(
97				"RowShape with fingerprint {:?} not found for table {}",
98				fingerprint,
99				self.table.def().name
100			)))
101		})?;
102
103		self.shape = Some(shape.clone());
104
105		Ok(shape)
106	}
107}
108
109impl QueryNode for TableScanNode {
110	#[instrument(level = "trace", skip_all, name = "volcano::scan::table::initialize")]
111	fn initialize<'a>(&mut self, _rx: &mut Transaction<'a>, _ctx: &QueryContext) -> Result<()> {
112		Ok(())
113	}
114
115	#[instrument(level = "trace", skip_all, name = "volcano::scan::table::next")]
116	fn next<'a>(&mut self, rx: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<Columns>> {
117		reifydb_assertions! {
118			assert!(self.context.is_some(), "TableScanNode::next() called before initialize()");
119		}
120		let stored_ctx = self.context.as_ref().unwrap();
121
122		if self.exhausted {
123			return Ok(None);
124		}
125
126		let batch_size = stored_ctx.batch_size;
127
128		let range = RowKeyRange::scan_range(self.table.def().id.into(), self.last_key.as_ref());
129
130		let mut batch_rows = Vec::new();
131		let mut row_numbers = Vec::new();
132		let mut new_last_key = None;
133
134		let scope = match self.min_commit_version {
135			Some(v) => RangeScope::After(v),
136			None => RangeScope::All,
137		};
138
139		let mut stream = rx.range(range, scope, batch_size as usize)?;
140
141		for _ in 0..batch_size {
142			match stream.next() {
143				Some(Ok(multi)) => {
144					if let Some(key) = RowKey::decode(&multi.key) {
145						batch_rows.push(multi.row);
146						row_numbers.push(key.row);
147						new_last_key = Some(multi.key);
148					}
149				}
150				Some(Err(e)) => return Err(e),
151				None => {
152					self.exhausted = true;
153					break;
154				}
155			}
156		}
157
158		drop(stream);
159
160		if batch_rows.is_empty() {
161			self.exhausted = true;
162			if self.last_key.is_none() {
163				let columns: Vec<ColumnWithName> = self
164					.table
165					.columns()
166					.iter()
167					.map(|col| ColumnWithName {
168						name: Fragment::internal(&col.name),
169						data: ColumnBuffer::none_typed(col.constraint.get_type(), 0),
170					})
171					.collect();
172				return Ok(Some(Columns::new(columns)));
173			}
174			return Ok(None);
175		}
176
177		self.last_key = new_last_key;
178
179		let storage_columns: Vec<ColumnWithName> = {
180			self.table
181				.columns()
182				.iter()
183				.enumerate()
184				.map(|(idx, col)| ColumnWithName {
185					name: Fragment::internal(&col.name),
186					data: ColumnBuffer::with_capacity(self.storage_types[idx].clone(), 0),
187				})
188				.collect()
189		};
190
191		let mut columns = Columns::with_system_columns(storage_columns, Vec::new(), Vec::new(), Vec::new());
192		{
193			let shape = self.get_or_load_shape(rx, &batch_rows[0])?;
194			columns.append_rows(&shape, batch_rows.into_iter(), row_numbers.clone())?;
195		}
196
197		columns.row_numbers = CowVec::new(row_numbers);
198
199		decode_dictionary_columns(&mut columns, &self.dictionaries, rx)?;
200
201		Ok(Some(columns))
202	}
203
204	fn headers(&self) -> Option<ColumnHeaders> {
205		Some(self.headers.clone())
206	}
207}