reifydb_engine/vm/volcano/scan/
queue.rs1use std::sync::Arc;
5
6use arrow_array::{ArrayRef, RecordBatch};
7use arrow_schema::FieldRef;
8use reifydb_codec::row::{bytes::EncodedBytes, queue::EncodedQueueRow, shape::RowShape};
9use reifydb_core::{
10 common::TimeSource,
11 interface::{catalog::dictionary::Dictionary, resolved::ResolvedQueue, store::MultiVersionRow},
12 internal_error,
13 key::{any::TaggedKey, bound::TaggedKeyBoundRange, row::RowKeyRange},
14 value::{
15 batch::{append_rows, batch},
16 column::{builder::ColumnBuilder, headers::ColumnHeaders},
17 },
18};
19use reifydb_transaction::{multi::RangeScope, transaction::Transaction};
20use reifydb_value::value::{row_number::RowNumber, system_columns::SystemColumn, value_type::ValueType};
21use tracing::instrument;
22
23use super::{
24 super::{decode_dictionary_columns, user_pairs, with_user_columns},
25 empty_scan, scan_headers, source_system_columns,
26};
27use crate::{
28 Result,
29 vm::volcano::query::{QueryContext, QueryNode},
30};
31
32type DrainedBatch = (Vec<EncodedBytes>, Vec<RowNumber>, Option<TaggedKey>, bool);
33
34pub struct QueueScan {
35 queue: ResolvedQueue,
36 headers: ColumnHeaders,
37 shape: Option<RowShape>,
38 storage_types: Vec<ValueType>,
39 dictionaries: Vec<Option<(Dictionary, ValueType)>>,
40 last_key: Option<TaggedKey>,
41 exhausted: bool,
42 context: Option<Arc<QueryContext>>,
43 system_columns: Vec<SystemColumn>,
44}
45
46impl QueueScan {
47 pub fn new(queue: ResolvedQueue, context: Arc<QueryContext>, rx: &mut Transaction<'_>) -> Result<Self> {
48 let mut storage_types = Vec::with_capacity(queue.columns().len());
49 let mut dictionaries = Vec::with_capacity(queue.columns().len());
50
51 for col in queue.columns() {
52 if let Some(dict_id) = col.dictionary_id
53 && let Some(dict) = context.services.catalog.find_dictionary(rx, dict_id)?
54 {
55 storage_types.push(ValueType::DictionaryId);
56 dictionaries.push(Some((dict, col.constraint.get_type())));
57 continue;
58 }
59 storage_types.push(col.constraint.get_type());
60 dictionaries.push(None);
61 }
62
63 let system_columns = source_system_columns(false, queue.def().time != TimeSource::None, false);
64 let headers = scan_headers(queue.columns().iter().map(|col| col.name.as_str()), &system_columns);
65
66 Ok(Self {
67 queue,
68 headers,
69 shape: None,
70 storage_types,
71 dictionaries,
72 last_key: None,
73 exhausted: false,
74 context: Some(context),
75 system_columns,
76 })
77 }
78
79 fn get_or_load_shape(&mut self, rx: &mut Transaction, first: &EncodedBytes) -> Result<RowShape> {
80 if let Some(shape) = &self.shape {
81 return Ok(shape.clone());
82 }
83
84 let fingerprint = EncodedQueueRow::view(first).fingerprint();
85 let stored_ctx = self.context.as_ref().expect("QueueScan context not set");
86 let shape = stored_ctx.services.catalog.get_or_load_row_shape(fingerprint, rx)?.ok_or_else(|| {
87 internal_error!(
88 "RowShape with fingerprint {:?} not found for queue {}",
89 fingerprint,
90 self.queue.def().name
91 )
92 })?;
93
94 self.shape = Some(shape.clone());
95
96 Ok(shape)
97 }
98
99 #[instrument(level = "trace", skip_all, name = "volcano::scan::queue::range_open")]
100 fn open_range<'rx, 'tx>(
101 rx: &'rx mut Transaction<'tx>,
102 range: TaggedKeyBoundRange,
103 batch_size: u64,
104 ) -> Result<Box<dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>> + Send + 'rx>> {
105 rx.range_rev(range, RangeScope::All, batch_size as usize)
106 }
107
108 #[instrument(level = "trace", skip_all, name = "volcano::scan::queue::drain")]
109 fn drain_batch(
110 stream: &mut dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>>,
111 batch_size: u64,
112 ) -> Result<DrainedBatch> {
113 let mut batch: Vec<EncodedBytes> = Vec::new();
114 let mut row_numbers: Vec<RowNumber> = Vec::new();
115 let mut new_last_key = None;
116 let mut drained = false;
117
118 for _ in 0..batch_size {
119 match stream.next() {
120 Some(Ok(multi)) => {
121 if let TaggedKey::Row(key) = &multi.key {
122 row_numbers.push(key.row);
123 batch.push(multi.bytes);
124 new_last_key = Some(multi.key);
125 }
126 }
127 Some(Err(e)) => return Err(e),
128 None => {
129 drained = true;
130 break;
131 }
132 }
133 }
134
135 Ok((batch, row_numbers, new_last_key, drained))
136 }
137
138 #[instrument(level = "trace", skip_all, name = "volcano::scan::queue::column_alloc")]
139 fn storage_columns(&self, shape: &RowShape, declared: usize) -> Result<Vec<(FieldRef, ArrayRef)>> {
140 let mut storage_columns: Vec<(FieldRef, ArrayRef)> = self
141 .queue
142 .columns()
143 .iter()
144 .enumerate()
145 .map(|(idx, col)| {
146 ColumnBuilder::with_capacity(self.storage_types[idx].clone(), 0).finish(&col.name)
147 })
148 .collect();
149
150 for index in declared..shape.field_count() {
151 let field = shape.get_field(index).ok_or_else(|| {
152 internal_error!("queue {} shape lost field {}", self.queue.def().name, index)
153 })?;
154 storage_columns
155 .push(ColumnBuilder::with_capacity(field.constraint.get_type(), 0).finish(&field.name));
156 }
157
158 Ok(storage_columns)
159 }
160
161 #[instrument(level = "trace", skip_all, name = "volcano::scan::queue::append_rows")]
162 fn append_batch(
163 shape: &RowShape,
164 columns: RecordBatch,
165 bytes_vec: Vec<EncodedBytes>,
166 row_numbers: Vec<RowNumber>,
167 ) -> Result<RecordBatch> {
168 append_rows(columns, shape, bytes_vec, row_numbers)
169 }
170
171 fn enqueue_order_range(&self) -> TaggedKeyBoundRange {
172 RowKeyRange::scan_range(self.queue.def().id.into(), None).resume_before(self.last_key.as_ref())
173 }
174
175 fn empty_declared_columns(&self) -> Result<RecordBatch> {
176 empty_scan(
177 self.queue
178 .columns()
179 .iter()
180 .map(|col| ColumnBuilder::with_capacity(col.constraint.get_type(), 0).finish(&col.name))
181 .collect(),
182 &self.system_columns,
183 )
184 }
185}
186
187impl QueryNode for QueueScan {
188 #[instrument(level = "trace", skip_all, name = "volcano::scan::queue::initialize")]
189 fn initialize<'a>(&mut self, _rx: &mut Transaction<'a>, _ctx: &QueryContext) -> Result<()> {
190 Ok(())
191 }
192
193 #[instrument(level = "trace", skip_all, name = "volcano::scan::queue::next")]
194 fn next<'a>(&mut self, rx: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<RecordBatch>> {
195 if self.exhausted {
196 return Ok(None);
197 }
198
199 let batch_size = self.context.as_ref().expect("QueueScan context not set").batch_size;
200 let range = self.enqueue_order_range();
201
202 let (batch_rows, row_numbers, new_last_key, drained) = {
203 let mut stream = Self::open_range(rx, range, batch_size)?;
204 Self::drain_batch(&mut stream, batch_size)?
205 };
206
207 if drained {
208 self.exhausted = true;
209 }
210
211 if batch_rows.is_empty() {
212 self.exhausted = true;
213 if self.last_key.is_none() {
214 return Ok(Some(self.empty_declared_columns()?));
215 }
216 return Ok(None);
217 }
218
219 self.last_key = new_last_key;
220
221 let shape = self.get_or_load_shape(rx, &batch_rows[0])?;
222 let declared = self.queue.columns().len();
223
224 let storage_columns = self.storage_columns(&shape, declared)?;
225
226 let columns = batch(storage_columns)?;
227 let columns = Self::append_batch(&shape, columns, batch_rows, row_numbers)?;
228
229 let columns = decode_dictionary_columns(columns, &self.dictionaries, rx)?;
230
231 let mut user = user_pairs(&columns);
232 user.truncate(declared);
233
234 Ok(Some(with_user_columns(user, &columns)?))
235 }
236
237 fn headers(&self) -> Option<ColumnHeaders> {
238 Some(self.headers.clone())
239 }
240}