1use crate::pipeline::apply::event::PositionedEvent;
8use crate::pipeline::apply::feed::ChangeFeed;
9use crate::pipeline::apply::opts::ApplyOpts;
10use crate::pipeline::apply::runtime::{
11 apply_changes_with, apply_relation_changes_with, apply_transformed_sink_events,
12 write_relations_with, write_rows_with, ApplyContext,
13};
14use crate::pipeline::apply::transform::BatchTransformer;
15use crate::pipeline::pipeline::Pipeline;
16use anyhow::{Context, Result};
17use std::future::Future;
18use std::pin::Pin;
19use std::sync::Arc;
20use std::time::{Duration, Instant};
21use surreal_sync_core::SurrealSink;
22use surreal_sync_core::{Change, Relation, RelationChange, Row};
23use tracing::debug;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum ControlSignal {
31 SchemaRefresh,
33 AdHocSnapshot {
35 tables: Vec<String>,
37 },
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum StopReason {
43 Cancelled,
45 Deadline,
47 Until,
49 Finished,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub enum CheckpointPolicy {
61 #[default]
64 PersistAfterAdvance,
65 AdvanceOnly,
68 IntervalWhenDrained {
85 interval: Duration,
87 },
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92pub enum RuntimeExit {
93 Stopped(StopReason),
95}
96
97#[async_trait::async_trait]
103pub trait AdhocApply: Send + Sync {
104 async fn write_rows(&self, rows: Vec<Row>) -> Result<()>;
106
107 async fn write_relations(&self, relations: Vec<Relation>) -> Result<()>;
109
110 async fn apply_changes(&self, changes: Vec<Change>) -> Result<()>;
112
113 async fn apply_relation_changes(&self, changes: Vec<RelationChange>) -> Result<()>;
115
116 fn apply_opts(&self) -> &ApplyOpts;
118}
119
120struct AdhocApplyImpl<'a, S, T> {
121 sink: &'a S,
122 transformer: Arc<T>,
123 apply_opts: &'a ApplyOpts,
124}
125
126#[async_trait::async_trait]
127impl<'a, S, T> AdhocApply for AdhocApplyImpl<'a, S, T>
128where
129 S: SurrealSink,
130 T: BatchTransformer + 'static,
131{
132 async fn write_rows(&self, rows: Vec<Row>) -> Result<()> {
133 write_rows_with(
134 self.sink,
135 Arc::clone(&self.transformer),
136 rows,
137 self.apply_opts,
138 )
139 .await
140 }
141
142 async fn write_relations(&self, relations: Vec<Relation>) -> Result<()> {
143 write_relations_with(
144 self.sink,
145 Arc::clone(&self.transformer),
146 relations,
147 self.apply_opts,
148 )
149 .await
150 }
151
152 async fn apply_changes(&self, changes: Vec<Change>) -> Result<()> {
153 apply_changes_with(
154 self.sink,
155 Arc::clone(&self.transformer),
156 changes,
157 self.apply_opts,
158 )
159 .await
160 }
161
162 async fn apply_relation_changes(&self, changes: Vec<RelationChange>) -> Result<()> {
163 apply_relation_changes_with(
164 self.sink,
165 Arc::clone(&self.transformer),
166 changes,
167 self.apply_opts,
168 )
169 .await
170 }
171
172 fn apply_opts(&self) -> &ApplyOpts {
173 self.apply_opts
174 }
175}
176
177#[async_trait::async_trait]
190pub trait SourceDriver: Send {
191 type Position: Clone + Send + Sync + 'static;
193
194 async fn poll_work(&mut self) -> Result<Vec<PositionedEvent<Self::Position>>>;
196
197 async fn advance_watermark(&mut self, position: Self::Position) -> Result<()>;
201
202 fn is_finished(&self) -> bool {
204 false
205 }
206
207 async fn between_events(&mut self) -> Result<Vec<ControlSignal>> {
210 Ok(Vec::new())
211 }
212
213 async fn on_schema_refresh(&mut self) -> Result<()> {
215 Ok(())
216 }
217
218 async fn on_adhoc_snapshot(
226 &mut self,
227 _tables: &[String],
228 _apply: &dyn AdhocApply,
229 ) -> Result<()> {
230 Ok(())
231 }
232
233 fn stop_reason(&self) -> Option<StopReason> {
235 None
236 }
237
238 fn checkpoint_policy(&self) -> CheckpointPolicy {
240 CheckpointPolicy::PersistAfterAdvance
241 }
242
243 async fn persist_checkpoint(&mut self, _position: Self::Position) -> Result<()> {
247 Ok(())
248 }
249
250 async fn read_progress_for_persist(&mut self) -> Result<Option<Self::Position>> {
258 Ok(None)
259 }
260
261 fn note_sunk_events(&mut self, _count: u64) {}
274}
275
276pub struct ChangeFeedDriver<F> {
278 pub inner: F,
280}
281
282impl<F> ChangeFeedDriver<F> {
283 pub fn new(inner: F) -> Self {
285 Self { inner }
286 }
287
288 pub fn inner(&self) -> &F {
290 &self.inner
291 }
292
293 pub fn inner_mut(&mut self) -> &mut F {
295 &mut self.inner
296 }
297
298 pub fn into_inner(self) -> F {
300 self.inner
301 }
302}
303
304#[async_trait::async_trait]
305impl<F> SourceDriver for ChangeFeedDriver<F>
306where
307 F: ChangeFeed,
308{
309 type Position = F::Position;
310
311 async fn poll_work(&mut self) -> Result<Vec<PositionedEvent<Self::Position>>> {
312 let changes = self.inner.poll_changes().await?;
313 Ok(changes.into_iter().map(PositionedEvent::from).collect())
314 }
315
316 async fn advance_watermark(&mut self, position: Self::Position) -> Result<()> {
317 self.inner.advance_watermark(position).await
318 }
319
320 fn is_finished(&self) -> bool {
321 self.inner.is_finished()
322 }
323}
324
325pub struct ChangeFeedRef<'a, F: ChangeFeed> {
327 inner: &'a mut F,
328}
329
330impl<'a, F: ChangeFeed> ChangeFeedRef<'a, F> {
331 pub fn new(inner: &'a mut F) -> Self {
333 Self { inner }
334 }
335}
336
337#[async_trait::async_trait]
338impl<'a, F> SourceDriver for ChangeFeedRef<'a, F>
339where
340 F: ChangeFeed,
341{
342 type Position = F::Position;
343
344 async fn poll_work(&mut self) -> Result<Vec<PositionedEvent<Self::Position>>> {
345 let changes = self.inner.poll_changes().await?;
346 Ok(changes.into_iter().map(PositionedEvent::from).collect())
347 }
348
349 async fn advance_watermark(&mut self, position: Self::Position) -> Result<()> {
350 self.inner.advance_watermark(position).await
351 }
352
353 fn is_finished(&self) -> bool {
354 self.inner.is_finished()
355 }
356}
357
358#[derive(Debug, Clone, Default)]
362pub struct SourceRuntimeOpts {
363 pub deadline: Option<Instant>,
365 pub cancelled: bool,
367}
368
369impl SourceRuntimeOpts {
370 pub fn new() -> Self {
372 Self::default()
373 }
374
375 pub fn with_deadline(mut self, deadline: Instant) -> Self {
377 self.deadline = Some(deadline);
378 self
379 }
380
381 pub fn with_cancelled(mut self, cancelled: bool) -> Self {
383 self.cancelled = cancelled;
384 self
385 }
386}
387
388pub async fn run_source_runtime<D, S>(
396 driver: &mut D,
397 sink: &S,
398 pipeline: &Pipeline,
399 apply_opts: &ApplyOpts,
400 runtime_opts: &SourceRuntimeOpts,
401) -> Result<RuntimeExit>
402where
403 D: SourceDriver,
404 S: SurrealSink,
405{
406 run_source_runtime_with(
407 driver,
408 sink,
409 Arc::new(pipeline.clone()),
410 apply_opts,
411 runtime_opts,
412 )
413 .await
414}
415
416pub async fn run_source_runtime_with<D, S, T>(
426 driver: &mut D,
427 sink: &S,
428 transformer: Arc<T>,
429 apply_opts: &ApplyOpts,
430 runtime_opts: &SourceRuntimeOpts,
431) -> Result<RuntimeExit>
432where
433 D: SourceDriver,
434 S: SurrealSink,
435 T: BatchTransformer + 'static,
436{
437 let mut ctx = ApplyContext::new(sink, Arc::clone(&transformer), apply_opts);
438 let mut sinking: Option<PendingSink<'_, D::Position>> = None;
442
443 loop {
444 if let Some(reason) = effective_stop_reason(driver, runtime_opts) {
445 finish_pending_sink(&mut ctx, driver, &mut sinking).await?;
446 ctx.flush_for_driver(driver).await?;
447 return Ok(RuntimeExit::Stopped(reason));
448 }
449
450 let signals = driver.between_events().await.context("between_events")?;
453 if !signals.is_empty() {
454 finish_pending_sink(&mut ctx, driver, &mut sinking).await?;
455 handle_control_signals(driver, &mut ctx, sink, &transformer, apply_opts, signals)
456 .await?;
457 }
458
459 loop {
462 if let Some(reason) = effective_stop_reason(driver, runtime_opts) {
463 finish_pending_sink(&mut ctx, driver, &mut sinking).await?;
464 ctx.flush_for_driver(driver).await?;
465 return Ok(RuntimeExit::Stopped(reason));
466 }
467
468 ctx.poll_join_ready_public().await?;
469 try_launch_sink(sink, &mut ctx, &mut sinking)?;
470
471 if ctx.window_occupancy() >= apply_opts.max_in_flight {
472 break;
473 }
474
475 while ctx.buffer_len() < apply_opts.batch_size && !driver.is_finished() {
480 let polled = driver.poll_work().await.context("poll_work")?;
481 if polled.is_empty() {
482 break;
483 }
484 for pe in polled {
485 ctx.push_buffered_event(pe);
486 }
487 }
488
489 let started = if ctx.buffer_len() >= apply_opts.batch_size {
490 ctx.try_start_full_batch()
491 } else if ctx.buffer_len() > 0
492 && (driver.is_finished() || ctx.should_flush_partial_public())
493 {
494 ctx.try_start_partial_batch()
495 } else {
496 false
497 };
498
499 if !started {
500 break;
501 }
502 ctx.poll_join_ready_public().await?;
505 try_launch_sink(sink, &mut ctx, &mut sinking)?;
506 }
507
508 let has_transform = ctx.in_flight_count() > 0;
510 let has_sink = sinking.is_some();
511 let has_buffer = ctx.buffer_len() > 0;
512 let finished = driver.is_finished();
513
514 if !has_transform && !has_sink && !has_buffer {
515 if finished {
516 ctx.flush_for_driver(driver).await?;
517 return Ok(RuntimeExit::Stopped(StopReason::Finished));
518 }
519 if let Some(reason) = effective_stop_reason(driver, runtime_opts) {
520 ctx.flush_for_driver(driver).await?;
521 return Ok(RuntimeExit::Stopped(reason));
522 }
523 ctx.try_interval_persist_public(driver).await?;
524 tokio::time::sleep(apply_opts.batch_max_wait.min(Duration::from_millis(10))).await;
525 continue;
526 }
527
528 if !has_transform && !has_sink {
529 tokio::time::sleep(apply_opts.batch_max_wait.min(Duration::from_millis(10))).await;
531 continue;
532 }
533
534 if has_sink && ctx.window_occupancy() < apply_opts.max_in_flight && !finished {
537 let idle = apply_opts.batch_max_wait.min(Duration::from_millis(10));
538 if has_transform {
539 tokio::select! {
541 biased;
542 outcome = ctx.wait_one_completion_public() => {
543 outcome?;
544 try_launch_sink(sink, &mut ctx, &mut sinking)?;
545 }
546 result = poll_pending_sink(&mut sinking) => {
547 complete_pending_sink(&mut ctx, driver, &mut sinking, result).await?;
548 ctx.try_interval_persist_public(driver).await?;
549 try_launch_sink(sink, &mut ctx, &mut sinking)?;
550 }
551 _ = tokio::time::sleep(idle) => {}
552 }
553 } else {
554 tokio::select! {
558 result = poll_pending_sink(&mut sinking) => {
559 complete_pending_sink(&mut ctx, driver, &mut sinking, result).await?;
560 ctx.try_interval_persist_public(driver).await?;
561 try_launch_sink(sink, &mut ctx, &mut sinking)?;
562 }
563 _ = tokio::time::sleep(idle) => {}
564 }
565 }
566 continue;
567 }
568
569 tokio::select! {
570 biased;
571 outcome = ctx.wait_one_completion_public(), if has_transform => {
572 outcome?;
573 try_launch_sink(sink, &mut ctx, &mut sinking)?;
574 }
575 result = poll_pending_sink(&mut sinking), if has_sink => {
578 complete_pending_sink(&mut ctx, driver, &mut sinking, result).await?;
579 ctx.try_interval_persist_public(driver).await?;
580 try_launch_sink(sink, &mut ctx, &mut sinking)?;
581 }
582 }
583 }
584}
585
586struct PendingSinkMeta<P> {
587 batch_id: u64,
588 last_position: P,
589 event_count: u64,
591 sunk: u64,
593}
594
595struct PendingSink<'s, P> {
597 meta: PendingSinkMeta<P>,
598 drive: Pin<Box<dyn Future<Output = Result<SinkDrive>> + Send + 's>>,
599}
600
601fn try_launch_sink<'s, S, T, P>(
602 sink: &'s S,
603 ctx: &mut ApplyContext<'_, S, T, P>,
604 sinking: &mut Option<PendingSink<'s, P>>,
605) -> Result<()>
606where
607 S: SurrealSink,
608 T: BatchTransformer + 'static,
609 P: Clone + Send + Sync + 'static,
610{
611 if sinking.is_some() {
612 return Ok(());
613 }
614 let Some(batch) = ctx.prepare_ordered_sink() else {
615 return Ok(());
616 };
617 match batch.result {
618 Ok(events) => {
619 let event_count = batch.event_count;
622 let drive = Box::pin(async move {
623 apply_transformed_sink_events(sink, &events).await?;
624 Ok(SinkDrive::Applied)
625 });
626 *sinking = Some(PendingSink {
627 meta: PendingSinkMeta {
628 batch_id: batch.batch_id,
629 last_position: batch.last_position,
630 event_count,
631 sunk: event_count,
632 },
633 drive,
634 });
635 }
636 Err(e) => {
637 let drive = Box::pin(async move { Ok(SinkDrive::TransformFailed(e)) });
638 *sinking = Some(PendingSink {
639 meta: PendingSinkMeta {
640 batch_id: batch.batch_id,
641 last_position: batch.last_position,
642 event_count: batch.event_count,
643 sunk: 0,
644 },
645 drive,
646 });
647 }
648 }
649 Ok(())
650}
651
652enum SinkDrive {
653 Applied,
654 TransformFailed(anyhow::Error),
655}
656
657async fn poll_pending_sink<'s, P>(sinking: &mut Option<PendingSink<'s, P>>) -> Result<SinkDrive> {
659 match sinking.as_mut() {
660 Some(pending) => pending.drive.as_mut().await,
661 None => std::future::pending().await,
662 }
663}
664
665async fn complete_pending_sink<D, S, T>(
666 ctx: &mut ApplyContext<'_, S, T, D::Position>,
667 driver: &mut D,
668 sinking: &mut Option<PendingSink<'_, D::Position>>,
669 result: Result<SinkDrive>,
670) -> Result<()>
671where
672 D: SourceDriver,
673 S: SurrealSink,
674 T: BatchTransformer + 'static,
675{
676 let pending = sinking.take().expect("sink slot");
677 match result {
678 Ok(SinkDrive::Applied) => {
679 ctx.finish_sink_ok_driver(driver, pending.meta.last_position, pending.meta.sunk)
680 .await
681 }
682 Ok(SinkDrive::TransformFailed(e)) => {
683 ctx.finish_sink_err_driver(
684 driver,
685 pending.meta.batch_id,
686 pending.meta.last_position,
687 pending.meta.event_count,
688 e,
689 )
690 .await
691 }
692 Err(e) => {
693 ctx.finish_sink_err_driver(
694 driver,
695 pending.meta.batch_id,
696 pending.meta.last_position,
697 pending.meta.event_count,
698 e,
699 )
700 .await
701 }
702 }
703}
704
705async fn finish_pending_sink<D, S, T>(
706 ctx: &mut ApplyContext<'_, S, T, D::Position>,
707 driver: &mut D,
708 sinking: &mut Option<PendingSink<'_, D::Position>>,
709) -> Result<()>
710where
711 D: SourceDriver,
712 S: SurrealSink,
713 T: BatchTransformer + 'static,
714{
715 if sinking.is_none() {
716 return Ok(());
717 }
718 let result = poll_pending_sink(sinking).await;
719 complete_pending_sink(ctx, driver, sinking, result).await
720}
721
722fn effective_stop_reason<D: SourceDriver>(
723 driver: &D,
724 runtime_opts: &SourceRuntimeOpts,
725) -> Option<StopReason> {
726 if runtime_opts.cancelled {
727 return Some(StopReason::Cancelled);
728 }
729 if let Some(deadline) = runtime_opts.deadline {
730 if Instant::now() >= deadline {
731 return Some(StopReason::Deadline);
732 }
733 }
734 driver.stop_reason()
735}
736
737async fn handle_control_signals<D, S, T>(
738 driver: &mut D,
739 ctx: &mut ApplyContext<'_, S, T, D::Position>,
740 sink: &S,
741 transformer: &Arc<T>,
742 apply_opts: &ApplyOpts,
743 signals: Vec<ControlSignal>,
744) -> Result<()>
745where
746 D: SourceDriver,
747 S: SurrealSink,
748 T: BatchTransformer + 'static,
749{
750 ctx.flush_for_driver(driver).await?;
753 for signal in signals {
754 match signal {
755 ControlSignal::SchemaRefresh => {
756 debug!("SourceDriver schema refresh");
757 driver
758 .on_schema_refresh()
759 .await
760 .context("on_schema_refresh")?;
761 }
762 ControlSignal::AdHocSnapshot { tables } => {
763 debug!(?tables, "SourceDriver ad-hoc snapshot");
764 let apply = AdhocApplyImpl {
765 sink,
766 transformer: Arc::clone(transformer),
767 apply_opts,
768 };
769 driver
770 .on_adhoc_snapshot(&tables, &apply)
771 .await
772 .context("on_adhoc_snapshot")?;
773 }
774 }
775 }
776 Ok(())
777}