1use std::sync::Arc;
5
6use arrow_array::{Array, ArrayRef, RecordBatch, UInt64Array};
7use arrow_schema::{FieldRef, SchemaRef};
8use reifydb_codec::row::{bytes::EncodedBytes, series::EncodedSeriesRow, shape::RowShape};
9use reifydb_core::{
10 common::TimeSource,
11 interface::{
12 catalog::{series::Series, storage::StorageId},
13 resolved::ResolvedSeries,
14 store::MultiVersionRow,
15 },
16 key::{
17 any::TaggedKey,
18 bound::TaggedKeyBoundRange,
19 series::{PartitionedSeriesRowKeyRange, SeriesRowKeyRange},
20 },
21 value::{
22 batch::{batch, batch_with, decode_cells},
23 column::{
24 builder::ColumnBuilder,
25 factory::{datetime, none_typed, uint1},
26 headers::ColumnHeaders,
27 },
28 },
29};
30use reifydb_transaction::{multi::RangeScope, transaction::Transaction};
31use reifydb_value::{
32 reifydb_assertions,
33 value::{
34 datetime::DateTime,
35 partition::Partition,
36 system_columns::{SystemColumn, system_field},
37 value_type::ValueType,
38 },
39};
40use tracing::instrument;
41
42use super::{
43 empty_scan,
44 merge::{MergeLayout, PartitionMerge},
45 partition_array, scan_headers, source_system_columns,
46};
47use crate::{
48 Result,
49 vm::{
50 instruction::dml::shape::get_or_create_series_shape,
51 volcano::{
52 decode_dictionary_column,
53 query::{QueryContext, QueryNode},
54 },
55 },
56};
57
58pub struct SeriesScanNode {
59 series: ResolvedSeries,
60 key_range_start: Option<u64>,
61 key_range_end: Option<u64>,
62 variant_tag: Option<u8>,
63 partition: Option<Partition>,
64 context: Option<Arc<QueryContext>>,
65 headers: ColumnHeaders,
66 last_key: Option<TaggedKey>,
67 exhausted: bool,
68 system_columns: Vec<SystemColumn>,
69 oldest_first: bool,
70 merge: Option<PartitionMerge>,
71 schema: Option<SchemaRef>,
72 keep_dictionary_ids: bool,
73}
74
75impl SeriesScanNode {
76 pub fn new(
77 series: ResolvedSeries,
78 key_range_start: Option<u64>,
79 key_range_end: Option<u64>,
80 variant_tag: Option<u8>,
81 partition: Option<Partition>,
82 context: Arc<QueryContext>,
83 ) -> Result<Self> {
84 let mut columns = vec![series.def().key.column()];
85 if series.def().tag.is_some() {
86 columns.push("tag");
87 }
88 for col in series.columns() {
89 columns.push(col.name.as_str());
90 }
91 let system_columns = source_system_columns(
92 !series.def().partition_by.is_empty(),
93 series.def().time != TimeSource::None,
94 false,
95 );
96 let headers = scan_headers(columns.into_iter(), &system_columns);
97
98 Ok(Self {
99 series,
100 key_range_start,
101 key_range_end,
102 variant_tag,
103 partition,
104 context: Some(context),
105 headers,
106 last_key: None,
107 exhausted: false,
108 system_columns,
109 oldest_first: false,
110 keep_dictionary_ids: false,
111 merge: None,
112 schema: None,
113 })
114 }
115
116 pub(crate) fn oldest_first(mut self) -> Self {
117 self.oldest_first = true;
118 self
119 }
120
121 pub(crate) fn keep_dictionary_ids(mut self) -> Self {
122 self.keep_dictionary_ids = true;
123 self
124 }
125
126 #[instrument(level = "trace", skip_all, name = "volcano::scan::series::range_open")]
127 fn open_range<'rx, 'tx>(
128 rx: &'rx mut Transaction<'tx>,
129 range: TaggedKeyBoundRange,
130 scope: RangeScope,
131 batch_size: u64,
132 oldest_first: bool,
133 ) -> Result<Box<dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>> + Send + 'rx>> {
134 if oldest_first {
135 return rx.range_rev(range, scope, batch_size as usize);
136 }
137 rx.range(range, scope, batch_size as usize)
138 }
139
140 #[instrument(level = "trace", skip_all, name = "volcano::scan::series::drain")]
141 fn drain_batch(
142 stream: &mut dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>>,
143 batch_size: u64,
144 partitioned: bool,
145 has_tag: bool,
146 ) -> Result<SeriesBatch> {
147 let mut batch = SeriesBatch::default();
148 let mut count = 0;
149
150 for entry in stream {
151 let entry = entry?;
152
153 let decoded: Option<(u64, u64, Option<u8>, Option<Partition>)> = if partitioned {
154 match &entry.key {
155 TaggedKey::PartitionedSeriesRow(pk) => {
156 Some((pk.key, pk.sequence, pk.variant_tag, Some(pk.partition)))
157 }
158 _ => None,
159 }
160 } else {
161 match &entry.key {
162 TaggedKey::SeriesRow(k) => Some((k.key, k.sequence, k.variant_tag, None)),
163 _ => None,
164 }
165 };
166
167 if let Some((key_val, sequence, variant_tag, partition)) = decoded {
168 batch.key_values.push(key_val);
169 batch.sequences.push(sequence);
170 if let Some(p) = partition {
171 batch.partitions.push(p);
172 }
173 let row = EncodedSeriesRow::view(&entry.bytes);
174 batch.created_at_values.push(row.created_at());
175 if let Some(time) = row.time() {
176 batch.time_values.push(time);
177 }
178 batch.updated_at_values.push(row.updated_at());
179 if has_tag {
180 batch.tags.push(variant_tag.unwrap_or(0));
181 }
182
183 batch.rows.push(entry.bytes);
184
185 batch.last_key = Some(entry.key.clone());
186 count += 1;
187 if count >= batch_size as usize {
188 break;
189 }
190 }
191 }
192
193 Ok(batch)
194 }
195
196 #[instrument(level = "trace", skip_all, name = "volcano::scan::series::assemble")]
197 fn assemble<'a>(
198 &mut self,
199 rx: &mut Transaction<'a>,
200 stored_ctx: &QueryContext,
201 read_shape: &RowShape,
202 scanned: SeriesBatch,
203 has_tag: bool,
204 partitioned: bool,
205 ) -> Result<Option<RecordBatch>> {
206 let series = self.series.def();
207 let mut result_columns = Vec::new();
208
209 result_columns.push(series.key_column_data(scanned.key_values));
210
211 if has_tag {
212 result_columns.push(uint1("tag", scanned.tags));
213 }
214
215 for (col_idx, col_def) in series.data_columns().enumerate() {
216 let col_type = col_def.constraint.get_type();
217 let dictionary = match col_def.dictionary_id {
218 Some(dict_id) => stored_ctx.services.catalog.find_dictionary(rx, dict_id)?,
219 None => None,
220 };
221 match dictionary {
222 Some(dictionary) => {
223 let mut ids = ColumnBuilder::with_capacity(
224 ValueType::DictionaryId,
225 scanned.rows.len(),
226 );
227 decode_cells(&mut ids, &col_def.name, read_shape, col_idx + 1, &scanned.rows)?;
228 if self.keep_dictionary_ids {
229 ids.set_dictionary_id(dictionary.id);
230 result_columns.push(ids.finish(&col_def.name));
231 continue;
232 }
233 let (field, array) = ids.finish(&col_def.name);
234 result_columns.push(decode_dictionary_column(
235 &field,
236 &array,
237 &dictionary,
238 col_type,
239 rx,
240 )?);
241 }
242 None => {
243 let mut data = ColumnBuilder::with_capacity(col_type, scanned.rows.len());
244 decode_cells(&mut data, &col_def.name, read_shape, col_idx + 1, &scanned.rows)?;
245 result_columns.push(data.finish(&col_def.name));
246 }
247 }
248 }
249
250 let mut stamps: Vec<(SystemColumn, ArrayRef)> =
251 vec![(SystemColumn::RowNumbers, Arc::new(UInt64Array::from(scanned.sequences)))];
252 if partitioned {
253 stamps.push((SystemColumn::Partitions, partition_array(&scanned.partitions)));
254 }
255 stamps.push((
256 SystemColumn::CreatedAt,
257 datetime(SystemColumn::CreatedAt.name(), scanned.created_at_values).1,
258 ));
259 stamps.push((
260 SystemColumn::UpdatedAt,
261 datetime(SystemColumn::UpdatedAt.name(), scanned.updated_at_values).1,
262 ));
263 if !scanned.time_values.is_empty() {
264 stamps.push((SystemColumn::Time, datetime(SystemColumn::Time.name(), scanned.time_values).1));
265 }
266 let row_count = result_columns[0].1.len();
267 result_columns.extend(stamps
268 .into_iter()
269 .map(|(column, array)| (system_field(column, array.logical_null_count() > 0), array)));
270 let out = match &self.schema {
271 Some(schema) => batch_with(schema, result_columns, row_count)?,
272 None => batch(result_columns)?,
273 };
274 self.schema = Some(out.schema());
275 Ok(Some(out))
276 }
277}
278
279#[derive(Default)]
280struct SeriesBatch {
281 key_values: Vec<u64>,
282 tags: Vec<u8>,
283 sequences: Vec<u64>,
284 partitions: Vec<Partition>,
285 created_at_values: Vec<DateTime>,
286 time_values: Vec<DateTime>,
287 updated_at_values: Vec<DateTime>,
288 rows: Vec<EncodedBytes>,
289 last_key: Option<TaggedKey>,
290}
291
292pub(crate) fn empty_series_columns(series: &Series) -> Vec<(FieldRef, ArrayRef)> {
293 let key_type = series
294 .columns
295 .iter()
296 .find(|c| c.name == series.key.column())
297 .map(|c| c.constraint.get_type())
298 .unwrap_or(ValueType::Int8);
299
300 let mut result_columns = Vec::new();
301 result_columns.push(none_typed(series.key.column(), key_type, 0));
302 if series.tag.is_some() {
303 result_columns.push(none_typed("tag", ValueType::Uint1, 0));
304 }
305 for col_def in series.data_columns() {
306 result_columns.push(none_typed(&col_def.name, col_def.constraint.get_type(), 0));
307 }
308 result_columns
309}
310
311impl QueryNode for SeriesScanNode {
312 #[instrument(name = "volcano::scan::series::initialize", level = "trace", skip_all)]
313 fn initialize<'a>(&mut self, _rx: &mut Transaction<'a>, _ctx: &QueryContext) -> Result<()> {
314 Ok(())
315 }
316
317 #[instrument(name = "volcano::scan::series::next", level = "trace", skip_all)]
318 fn next<'a>(&mut self, rx: &mut Transaction<'a>, _ctx: &mut QueryContext) -> Result<Option<RecordBatch>> {
319 reifydb_assertions! {
320 assert!(self.context.is_some(), "SeriesScanNode::next() called before initialize()");
321 }
322 let stored_ctx = self.context.as_ref().unwrap();
323
324 if self.exhausted {
325 return Ok(None);
326 }
327
328 let batch_size = stored_ctx.batch_size;
329 let series = self.series.def();
330 let has_tag = series.tag.is_some();
331
332 if let (Some(start), Some(end)) = (self.key_range_start, self.key_range_end)
333 && start >= end
334 {
335 self.exhausted = true;
336 return Ok(Some(empty_scan(empty_series_columns(series), &self.system_columns)?));
337 }
338
339 let partitioned = !series.partition_by.is_empty();
340 let storage = StorageId::series(series.id);
341 let after = if self.oldest_first {
342 None
343 } else {
344 self.last_key.as_ref()
345 };
346 let range = if partitioned {
347 match self.partition {
348 Some(partition) => PartitionedSeriesRowKeyRange::scan_range(
349 storage,
350 partition,
351 has_tag,
352 self.variant_tag,
353 self.key_range_start,
354 self.key_range_end,
355 after,
356 ),
357 None => PartitionedSeriesRowKeyRange::full_scan_range(storage, after),
358 }
359 } else {
360 SeriesRowKeyRange::scan_range(
361 storage,
362 has_tag,
363 self.variant_tag,
364 self.key_range_start,
365 self.key_range_end,
366 after,
367 )
368 };
369 let range = if self.oldest_first {
370 range.resume_before(self.last_key.as_ref())
371 } else {
372 range
373 };
374
375 let read_shape = get_or_create_series_shape(&stored_ctx.services.catalog, self.series.def(), rx)?;
376 let stored_ctx = stored_ctx.clone();
377
378 let scope = RangeScope::All;
379
380 let batch = if self.oldest_first && partitioned {
381 let fixed = self.partition.map(|_| range);
382 let merge = self
383 .merge
384 .get_or_insert_with(|| PartitionMerge::new(MergeLayout::Series, storage, fixed));
385 let rows = merge.next(rx, batch_size)?;
386 Self::drain_batch(&mut rows.into_iter().map(Ok), batch_size, partitioned, has_tag)?
387 } else {
388 let mut stream = Self::open_range(rx, range, scope, batch_size, self.oldest_first)?;
389 Self::drain_batch(&mut stream, batch_size, partitioned, has_tag)?
390 };
391
392 if batch.key_values.is_empty() {
393 self.exhausted = true;
394 if self.last_key.is_none() {
395 return Ok(Some(empty_scan(
396 empty_series_columns(self.series.def()),
397 &self.system_columns,
398 )?));
399 }
400 return Ok(None);
401 }
402
403 self.last_key = batch.last_key.clone();
404
405 self.assemble(rx, &stored_ctx, &read_shape, batch, has_tag, partitioned)
406 }
407
408 fn headers(&self) -> Option<ColumnHeaders> {
409 Some(self.headers.clone())
410 }
411}