1use std::{collections::BTreeSet, num::NonZeroU64, sync::Arc};
5
6use reifydb_catalog::catalog::Catalog;
7use reifydb_codec::{key::encoded::EncodedKey, row::bytes::EncodedBytes};
8use reifydb_core::{
9 common::{ChangeVersion, CommitVersion},
10 flow::dag::FlowDag,
11 interface::{
12 catalog::{
13 config::{ConfigKey, GetConfig},
14 dictionary::Dictionary,
15 flow::OperatorId,
16 id::{TableId, ViewId},
17 object::ObjectId,
18 table::Table,
19 view::View,
20 },
21 change::{Change, ChangeOrigin, Diff},
22 },
23 internal_err,
24 key::any::TaggedKey,
25};
26use reifydb_flow::txn::{Batch, Emit, Find, Get, Intern, Resolve};
27#[cfg(feature = "testing")]
28use reifydb_flow_sync::testing::{InstalledHooks, TestingTxn};
29use reifydb_flow_sync::{
30 graph::build,
31 node::Node,
32 run::{flow_sources, run, run_flow},
33 txn::{Changes, ClockNow, Lookup},
34};
35use reifydb_runtime::context::RuntimeContext;
36use reifydb_transaction::transaction::Transaction;
37use reifydb_value::value::{
38 Value,
39 datetime::DateTime,
40 dictionary::{DictionaryEntryId, DictionaryId},
41};
42use smallvec::smallvec;
43
44use crate::{Result, backfill, transaction::operation::dictionary::DictionaryOperations, vm::services::Services};
45
46pub(crate) fn sync_transactional_views(services: &Services, tx: Transaction<'_>) -> Result<()> {
47 let mut txn = FlowTransaction {
48 tx,
49 catalog: &services.catalog,
50 runtime_context: &services.runtime_context,
51 };
52 #[cfg(feature = "testing")]
53 if let Some(InstalledHooks(hooks)) = services.ioc.try_resolve::<InstalledHooks>() {
54 return run(&mut TestingTxn::over(txn, hooks), &services.routines, &services.runtime_context);
55 }
56 run(&mut txn, &services.routines, &services.runtime_context)
57}
58
59pub(crate) fn load_flow_diff_need(catalog: &Catalog, tx: &mut Transaction<'_>) -> Result<()> {
60 if tx.flow_diff_need_loaded() {
61 return Ok(());
62 }
63 let mut objects = BTreeSet::new();
64 for flow in catalog.list_transactional_flow_dags(tx)?.iter() {
65 objects.extend(flow_sources(flow));
66 }
67 tx.set_flow_diff_need(objects);
68 Ok(())
69}
70
71pub(crate) fn backfill_transactional_view(
72 services: &Arc<Services>,
73 mut tx: Transaction<'_>,
74 view: ViewId,
75) -> Result<()> {
76 let flow = view_flow(&services.catalog, &mut tx, view)?;
77 let mut nodes = build_flow(services, tx.reborrow(), &flow)?;
78 backfill::run(services, tx.reborrow(), &flow_sources(&flow), query_batch_size(services)?, |scan, change| {
79 feed_flow(services, scan.transaction().reborrow(), &flow, &mut nodes, change)
80 })?;
81 let end = tx.flow_entries_from(0).len();
82 tx.set_flow_cursor(end);
83 Ok(())
84}
85
86fn view_flow(catalog: &Catalog, tx: &mut Transaction<'_>, view: ViewId) -> Result<FlowDag> {
87 match catalog.list_flow_dags_asc(tx)?.into_iter().find(|flow| flow.sink_views().any(|sink| sink == view)) {
88 Some(flow) => Ok(flow),
89 None => internal_err!("transactional view {:?} has no flow to backfill", view),
90 }
91}
92
93fn query_batch_size(services: &Services) -> Result<NonZeroU64> {
94 match NonZeroU64::new(u64::from(services.catalog.get_config_uint2(ConfigKey::QueryRowBatchSize))) {
95 Some(batch_size) => Ok(batch_size),
96 None => internal_err!("QUERY_ROW_BATCH_SIZE is 0, which its config validation rejects"),
97 }
98}
99
100fn build_flow(services: &Services, tx: Transaction<'_>, flow: &FlowDag) -> Result<Vec<(OperatorId, Node)>> {
101 let mut txn = FlowTransaction {
102 tx,
103 catalog: &services.catalog,
104 runtime_context: &services.runtime_context,
105 };
106 #[cfg(feature = "testing")]
107 if let Some(InstalledHooks(hooks)) = services.ioc.try_resolve::<InstalledHooks>() {
108 return build(&mut TestingTxn::over(txn, hooks), flow, &services.routines, &services.runtime_context);
109 }
110 build(&mut txn, flow, &services.routines, &services.runtime_context)
111}
112
113fn feed_flow(
114 services: &Services,
115 tx: Transaction<'_>,
116 flow: &FlowDag,
117 nodes: &mut [(OperatorId, Node)],
118 change: Change,
119) -> Result<()> {
120 let mut txn = FlowTransaction {
121 tx,
122 catalog: &services.catalog,
123 runtime_context: &services.runtime_context,
124 };
125 #[cfg(feature = "testing")]
126 if let Some(InstalledHooks(hooks)) = services.ioc.try_resolve::<InstalledHooks>() {
127 return run_flow(&mut TestingTxn::over(txn, hooks), flow, nodes, change);
128 }
129 run_flow(&mut txn, flow, nodes, change)
130}
131
132pub(crate) struct FlowTransaction<'a> {
133 tx: Transaction<'a>,
134 catalog: &'a Catalog,
135 runtime_context: &'a RuntimeContext,
136}
137
138fn tagged(key: &EncodedKey) -> TaggedKey {
139 TaggedKey::decode(key).unwrap_or_else(|| panic!("flow sync produced an undecodable key {key:?}"))
140}
141
142impl Changes for FlowTransaction<'_> {
143 fn cursor(&self) -> usize {
144 self.tx.flow_cursor()
145 }
146
147 fn entries_from(&self, at: usize) -> &[(ObjectId, Diff)] {
148 self.tx.flow_entries_from(at)
149 }
150
151 fn set_cursor(&mut self, at: usize) {
152 self.tx.set_flow_cursor(at)
153 }
154}
155
156impl Get for FlowTransaction<'_> {
157 fn get(&mut self, keys: &[EncodedKey]) -> Result<Vec<Option<EncodedBytes>>> {
158 let keys: Vec<TaggedKey> = keys.iter().map(tagged).collect();
159 Ok(self.tx.get(&keys)?.into_iter().map(|row| row.map(|row| row.bytes)).collect())
160 }
161}
162
163impl Batch for FlowTransaction<'_> {
164 fn set(&mut self, keys: Vec<EncodedKey>, rows: Vec<EncodedBytes>) -> Result<()> {
165 self.tx.set(keys.iter().map(tagged).zip(rows).collect())
166 }
167
168 fn remove(&mut self, keys: Vec<EncodedKey>) -> Result<()> {
169 let keys: Vec<TaggedKey> = keys.iter().map(tagged).collect();
170 let committed = self.tx.get_committed(&keys)?;
171 self.tx.remove(keys
172 .into_iter()
173 .zip(committed)
174 .map(|(key, committed)| (key, committed.map(|row| row.bytes)))
175 .collect())
176 }
177}
178
179impl Emit for FlowTransaction<'_> {
180 fn emit(&mut self, view: ViewId, diff: Diff) -> Result<()> {
181 let object = ObjectId::view(view);
182 self.tx.touch_flow_object(object);
183 if self.tx.wants_flow_diff(object) {
184 self.tx.track_flow_change(Change {
185 origin: ChangeOrigin::Object(object),
186 diffs: smallvec![diff],
187 version: ChangeVersion::from(CommitVersion(0)),
188 changed_at: DateTime::default(),
189 });
190 }
191 Ok(())
192 }
193}
194
195impl Lookup for FlowTransaction<'_> {
196 fn transactional_flows(&mut self) -> Result<Vec<FlowDag>> {
197 Ok(self.catalog.list_transactional_flow_dags(&mut self.tx)?.to_vec())
198 }
199
200 fn view(&mut self, id: ViewId) -> Result<View> {
201 self.catalog.get_view(&mut self.tx, id)
202 }
203
204 fn table(&mut self, id: TableId) -> Result<Table> {
205 self.catalog.get_table(&mut self.tx, id)
206 }
207}
208
209impl Resolve for FlowTransaction<'_> {
210 fn dictionary(&mut self, id: DictionaryId) -> Result<Option<Dictionary>> {
211 self.catalog.find_dictionary(&mut self.tx, id)
212 }
213
214 fn resolve(&mut self, dictionary: &Dictionary, ids: &[DictionaryEntryId]) -> Result<Vec<Option<Value>>> {
215 self.tx.get_from_dictionary(dictionary, ids)
216 }
217}
218
219impl Find for FlowTransaction<'_> {
220 fn find(&mut self, dictionary: &Dictionary, values: &[Value]) -> Result<Vec<Option<DictionaryEntryId>>> {
221 self.tx.find_in_dictionary(dictionary, values)
222 }
223}
224
225impl Intern for FlowTransaction<'_> {
226 fn intern(&mut self, dictionary: &Dictionary, values: &[Value]) -> Result<Vec<DictionaryEntryId>> {
227 self.tx.intern_values(dictionary, values.to_vec())
228 }
229}
230
231impl ClockNow for FlowTransaction<'_> {
232 fn now(&self) -> DateTime {
233 self.runtime_context.clock.now()
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use reifydb_core::{
240 interface::{
241 catalog::{id::ViewId, object::ObjectId},
242 change::Diff,
243 },
244 value::batch::single_row,
245 };
246 use reifydb_flow::txn::Emit;
247 use reifydb_flow_sync::txn::{Changes, Lookup};
248 use reifydb_test_harness::engine::create_test_admin_transaction;
249 use reifydb_transaction::transaction::{Transaction, admin::AdminTransaction};
250 use reifydb_value::{params::Params, value::Value};
251
252 use super::FlowTransaction;
253 use crate::vm::{Admin, executor::Executor};
254
255 fn admin(executor: &Executor, txn: &mut AdminTransaction, rql: &str) {
256 let r = executor.admin(
257 txn,
258 Admin {
259 rql,
260 params: Params::default(),
261 },
262 );
263 if let Some(e) = r.error {
264 panic!("{rql}: {e:?}");
265 }
266 }
267
268 #[test]
269 fn an_emitted_view_diff_lands_after_the_cursor_and_moving_the_cursor_skips_it() {
270 let executor = Executor::testing();
273 let mut txn = create_test_admin_transaction();
274 let mut sync = FlowTransaction {
275 tx: Transaction::Admin(&mut txn),
276 catalog: &executor.catalog,
277 runtime_context: &executor.runtime_context,
278 };
279 let at = sync.cursor();
280 let diff = Diff::insert(single_row([("id", Value::Int4(1))]).unwrap());
281 sync.emit(ViewId(7), diff.clone()).unwrap();
282 assert_eq!(sync.entries_from(at), vec![(ObjectId::view(ViewId(7)), diff)]);
283 sync.set_cursor(at + 1);
284 assert!(sync.entries_from(sync.cursor()).is_empty());
285 }
286
287 #[test]
288 fn a_transactional_view_created_in_the_same_txn_is_listed_and_a_deferred_one_is_not() {
289 let executor = Executor::testing();
291 let mut txn = create_test_admin_transaction();
292 admin(&executor, &mut txn, "CREATE NAMESPACE ns");
293 admin(&executor, &mut txn, "CREATE TABLE ns::src { id: int4 }");
294 admin(&executor, &mut txn, "CREATE DEFERRED VIEW ns::d { id: int4 } AS { FROM ns::src }");
295 admin(&executor, &mut txn, "CREATE TRANSACTIONAL VIEW ns::v { id: int4 } AS { FROM ns::src }");
296 let mut sync = FlowTransaction {
297 tx: Transaction::Admin(&mut txn),
298 catalog: &executor.catalog,
299 runtime_context: &executor.runtime_context,
300 };
301 let flows = sync.transactional_flows().unwrap();
302 assert_eq!(flows.len(), 1, "only the transactional view's flow may be listed");
303 }
304}