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