1use std::{
5 collections::{BTreeMap, BTreeSet},
6 num::NonZeroU64,
7 sync::Arc,
8};
9
10use arrow_array::RecordBatch;
11use reifydb_core::{
12 common::CommitVersion,
13 interface::{catalog::object::ObjectId, change::Change, resolved::ResolvedSeries},
14 internal_err,
15};
16use reifydb_evaluate::stack::SymbolTable;
17#[cfg(feature = "testing")]
18use reifydb_flow::backfill::testing::{InstalledScanHooks, TestingScan};
19use reifydb_flow::backfill::{Scan, backfill_partitioned};
20use reifydb_transaction::transaction::Transaction;
21use reifydb_value::{fragment::Fragment, params::Params, value::partition::Partition};
22
23use crate::{
24 Result,
25 vm::{
26 services::Services,
27 volcano::{
28 query::{QueryContext, QueryNode, query_budget},
29 scan::{
30 ringbuffer::RingBufferScan, series::SeriesScanNode, table::TableScanNode,
31 view::ViewScanNode,
32 },
33 },
34 },
35};
36
37#[cfg(test)]
38mod tests;
39
40pub fn run<'a>(
41 services: &Arc<Services>,
42 tx: Transaction<'a>,
43 sources: &BTreeSet<ObjectId>,
44 batch_size: NonZeroU64,
45 consume: impl FnMut(&mut TransactionScan<'a>, Change) -> Result<()>,
46) -> Result<()> {
47 let sources = sources.iter().map(|&source| (source, None)).collect();
48 run_partitioned(services, tx, &sources, batch_size, consume)
49}
50
51pub fn run_partitioned<'a>(
52 services: &Arc<Services>,
53 tx: Transaction<'a>,
54 sources: &BTreeMap<ObjectId, Option<Partition>>,
55 batch_size: NonZeroU64,
56 mut consume: impl FnMut(&mut TransactionScan<'a>, Change) -> Result<()>,
57) -> Result<()> {
58 let mut scan = TransactionScan {
59 services: Arc::clone(services),
60 tx,
61 opened: None,
62 };
63 #[cfg(feature = "testing")]
64 if let Some(InstalledScanHooks(hooks)) = services.ioc.try_resolve::<InstalledScanHooks>() {
65 return backfill_partitioned(
66 &mut TestingScan::over(scan, hooks),
67 sources,
68 batch_size,
69 |testing, change| consume(testing.scan_mut(), change),
70 );
71 }
72 backfill_partitioned(&mut scan, sources, batch_size, &mut consume)
73}
74
75pub struct TransactionScan<'a> {
76 services: Arc<Services>,
77 tx: Transaction<'a>,
78 opened: Option<Opened>,
79}
80
81struct Opened {
82 node: Box<dyn QueryNode>,
83 context: QueryContext,
84 tagged_series: bool,
85}
86
87impl<'a> TransactionScan<'a> {
88 pub fn transaction(&mut self) -> &mut Transaction<'a> {
89 &mut self.tx
90 }
91
92 fn node(
93 &mut self,
94 source: ObjectId,
95 partition: Option<Partition>,
96 context: &Arc<QueryContext>,
97 ) -> Result<(Box<dyn QueryNode>, bool)> {
98 let catalog = &self.services.catalog;
99 let tx = &mut self.tx;
100 match source {
101 ObjectId::Table(id) => {
102 let table = catalog.resolve_table(tx, id)?;
103 let node = TableScanNode::new(table, partition, Arc::clone(context), tx)?
104 .oldest_first()
105 .keep_dictionary_ids();
106 Ok((Box::new(node), false))
107 }
108 ObjectId::View(id) => {
109 let view = catalog.resolve_view(tx, id)?;
110 let node = ViewScanNode::new(view, partition, Arc::clone(context), tx)?
111 .oldest_first()
112 .keep_dictionary_ids();
113 Ok((Box::new(node), false))
114 }
115 ObjectId::RingBuffer(id) => {
116 let ringbuffer = catalog.resolve_ringbuffer(tx, id)?;
117 let node = RingBufferScan::new(ringbuffer, Arc::clone(context), tx)?
118 .oldest_first()
119 .keep_dictionary_ids();
120 Ok((Box::new(node), false))
121 }
122 ObjectId::Series(id) => {
123 let series = catalog.get_series(tx, id)?;
124 let namespace = catalog.resolve_namespace(tx, series.namespace)?;
125 let tagged = series.tag.is_some();
126 let series =
127 ResolvedSeries::new(Fragment::internal(series.name.clone()), namespace, series);
128 let node = SeriesScanNode::new(series, None, None, None, None, Arc::clone(context))?
129 .oldest_first()
130 .keep_dictionary_ids();
131 Ok((Box::new(node), tagged))
132 }
133 ObjectId::TableVirtual(_) | ObjectId::Dictionary(_) | ObjectId::Queue(_) => {
134 internal_err!("backfill cannot scan source {:?}: it has no row scan node", source)
135 }
136 }
137 }
138}
139
140impl Scan for TransactionScan<'_> {
141 fn version(&self) -> CommitVersion {
142 self.tx.version()
143 }
144
145 fn open(&mut self, source: ObjectId, batch_size: NonZeroU64) -> Result<()> {
146 self.open_partition(source, None, batch_size)
147 }
148
149 fn open_partition(
150 &mut self,
151 source: ObjectId,
152 partition: Option<Partition>,
153 batch_size: NonZeroU64,
154 ) -> Result<()> {
155 let context = Arc::new(QueryContext {
156 services: Arc::clone(&self.services),
157 source: None,
158 batch_size: batch_size.get(),
159 params: Params::None,
160 symbols: SymbolTable::new(),
161 identity: self.tx.identity(),
162 memory: query_budget(&self.services),
163 });
164 let (mut node, tagged_series) = self.node(source, partition, &context)?;
165 node.initialize(&mut self.tx, &context)?;
166 self.opened = Some(Opened {
167 node,
168 context: (*context).clone(),
169 tagged_series,
170 });
171 Ok(())
172 }
173
174 fn next(&mut self) -> Result<Option<RecordBatch>> {
175 let Some(opened) = self.opened.as_mut() else {
176 return internal_err!("transaction scan pulled before any source was opened");
177 };
178 let Some(mut chunk) = opened.node.next(&mut self.tx, &mut opened.context)? else {
179 return Ok(None);
180 };
181 if opened.tagged_series {
182 chunk.remove_column(1);
183 }
184 Ok(Some(chunk))
185 }
186}