use crate::pipeline::apply::ApplyEvent;
use crate::pipeline::external::ExternalTransform;
use anyhow::{bail, Result};
use std::sync::Arc;
use surreal_sync_core::InPlaceTransform;
use surreal_sync_core::{Change, Relation, RelationChange, Row};
#[derive(Clone)]
pub enum Stage {
InPlace(Arc<dyn InPlaceTransform>),
External(ExternalTransform),
}
impl std::fmt::Debug for Stage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Stage::InPlace(_) => f.write_str("InPlace(_)"),
Stage::External(ext) => f.debug_tuple("External").field(ext).finish(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct Pipeline {
stages: Vec<Stage>,
}
impl Pipeline {
pub fn new() -> Self {
Self { stages: Vec::new() }
}
pub fn is_identity(&self) -> bool {
self.stages.is_empty()
}
pub fn len(&self) -> usize {
self.stages.len()
}
pub fn is_empty(&self) -> bool {
self.stages.is_empty()
}
pub fn stages(&self) -> &[Stage] {
&self.stages
}
pub fn push_inplace<T>(&mut self, transform: T)
where
T: InPlaceTransform + 'static,
{
self.stages.push(Stage::InPlace(Arc::new(transform)));
}
pub fn push_inplace_arc(&mut self, transform: Arc<dyn InPlaceTransform>) {
self.stages.push(Stage::InPlace(transform));
}
pub fn push_external(&mut self, external: ExternalTransform) {
self.stages.push(Stage::External(external));
}
pub fn transform_rows_inplace(&self, rows: &mut [Row]) -> Result<()> {
if self.is_identity() {
return Ok(());
}
for stage in &self.stages {
match stage {
Stage::InPlace(t) => t.transform_rows_inplace(rows)?,
Stage::External(_) => {
bail!(
"External transforms require the async BatchTransformer path \
(transform_rows); sync inplace apply is in-place-only"
)
}
}
}
Ok(())
}
pub fn transform_changes_inplace(&self, changes: &mut [Change]) -> Result<()> {
if self.is_identity() {
return Ok(());
}
for stage in &self.stages {
match stage {
Stage::InPlace(t) => t.transform_changes_inplace(changes)?,
Stage::External(_) => {
bail!(
"External transforms require the async BatchTransformer path \
(transform_changes); sync inplace apply is in-place-only"
)
}
}
}
Ok(())
}
pub fn apply_rows(&self, mut rows: Vec<Row>) -> Result<Vec<Row>> {
self.transform_rows_inplace(&mut rows)?;
Ok(rows)
}
pub fn apply_changes(&self, mut changes: Vec<Change>) -> Result<Vec<Change>> {
self.transform_changes_inplace(&mut changes)?;
Ok(changes)
}
pub(crate) async fn apply_changes_async(
&self,
batch_id: u64,
mut changes: Vec<Change>,
) -> Result<Vec<Change>> {
if self.is_identity() {
return Ok(changes);
}
for stage in &self.stages {
match stage {
Stage::InPlace(t) => t.transform_changes_inplace(&mut changes)?,
Stage::External(ext) => {
changes = ext.exchange_changes(batch_id, changes).await?;
}
}
}
Ok(changes)
}
pub(crate) async fn apply_rows_async(
&self,
batch_id: u64,
mut rows: Vec<Row>,
) -> Result<Vec<Row>> {
if self.is_identity() {
return Ok(rows);
}
for stage in &self.stages {
match stage {
Stage::InPlace(t) => t.transform_rows_inplace(&mut rows)?,
Stage::External(ext) => {
rows = ext.exchange_rows(batch_id, rows).await?;
}
}
}
Ok(rows)
}
pub fn transform_relation_changes_inplace(&self, changes: &mut [RelationChange]) -> Result<()> {
if self.is_identity() {
return Ok(());
}
for stage in &self.stages {
match stage {
Stage::InPlace(t) => t.transform_relation_changes_inplace(changes)?,
Stage::External(_) => {
bail!(
"External transforms require the async BatchTransformer path \
(transform_relation_changes); sync inplace apply is in-place-only"
)
}
}
}
Ok(())
}
pub fn transform_relations_inplace(&self, relations: &mut [Relation]) -> Result<()> {
if self.is_identity() {
return Ok(());
}
for stage in &self.stages {
match stage {
Stage::InPlace(t) => t.transform_relations_inplace(relations)?,
Stage::External(_) => {
bail!(
"External transforms require the async BatchTransformer path \
(transform_relations); sync inplace apply is in-place-only"
)
}
}
}
Ok(())
}
pub fn apply_relation_changes(
&self,
mut changes: Vec<RelationChange>,
) -> Result<Vec<RelationChange>> {
self.transform_relation_changes_inplace(&mut changes)?;
Ok(changes)
}
pub fn apply_relations(&self, mut relations: Vec<Relation>) -> Result<Vec<Relation>> {
self.transform_relations_inplace(&mut relations)?;
Ok(relations)
}
pub(crate) async fn apply_relation_changes_async(
&self,
batch_id: u64,
mut changes: Vec<RelationChange>,
) -> Result<Vec<RelationChange>> {
if self.is_identity() {
return Ok(changes);
}
for stage in &self.stages {
match stage {
Stage::InPlace(t) => t.transform_relation_changes_inplace(&mut changes)?,
Stage::External(ext) => {
changes = ext.exchange_relation_changes(batch_id, changes).await?;
}
}
}
Ok(changes)
}
pub(crate) async fn apply_relations_async(
&self,
batch_id: u64,
mut relations: Vec<Relation>,
) -> Result<Vec<Relation>> {
if self.is_identity() {
return Ok(relations);
}
for stage in &self.stages {
match stage {
Stage::InPlace(t) => t.transform_relations_inplace(&mut relations)?,
Stage::External(ext) => {
relations = ext.exchange_relations(batch_id, relations).await?;
}
}
}
Ok(relations)
}
pub(crate) async fn apply_events_async(
&self,
batch_id: u64,
mut events: Vec<ApplyEvent>,
) -> Result<Vec<ApplyEvent>> {
if self.is_identity() {
return Ok(events);
}
for stage in &self.stages {
match stage {
Stage::InPlace(t) => {
for event in &mut events {
match event {
ApplyEvent::Change(c) => t.transform_change(c)?,
ApplyEvent::RelationChange(r) => t.transform_relation_change(r)?,
}
}
}
Stage::External(ext) => {
let all_changes = events.iter().all(|e| e.is_change());
let all_rels = events.iter().all(|e| e.is_relation_change());
if all_changes {
let changes: Vec<Change> = events
.into_iter()
.map(|e| match e {
ApplyEvent::Change(c) => c,
ApplyEvent::RelationChange(_) => unreachable!(),
})
.collect();
let transformed = ext.exchange_changes(batch_id, changes).await?;
events = transformed.into_iter().map(ApplyEvent::Change).collect();
} else if all_rels {
let rels: Vec<RelationChange> = events
.into_iter()
.map(|e| match e {
ApplyEvent::RelationChange(r) => *r,
ApplyEvent::Change(_) => unreachable!(),
})
.collect();
let transformed = ext.exchange_relation_changes(batch_id, rels).await?;
events = transformed
.into_iter()
.map(ApplyEvent::relation_change)
.collect();
} else {
let mut change_idxs = Vec::new();
let mut changes = Vec::new();
let mut rel_idxs = Vec::new();
let mut rels = Vec::new();
for (i, event) in events.iter().enumerate() {
match event {
ApplyEvent::Change(c) => {
change_idxs.push(i);
changes.push(c.clone());
}
ApplyEvent::RelationChange(r) => {
rel_idxs.push(i);
rels.push((**r).clone());
}
}
}
let rel_batch_id = crate::pipeline::relation_wire_batch_id(batch_id);
if !changes.is_empty() {
let n = changes.len();
let transformed = ext.exchange_changes(batch_id, changes).await?;
if transformed.len() != n {
bail!(
"External stage changed change-count ({n} → {}) while relation \
events were present in the same batch; use homogeneous batches \
for filter/fan-out",
transformed.len()
);
}
for (idx, c) in change_idxs.into_iter().zip(transformed) {
events[idx] = ApplyEvent::Change(c);
}
}
if !rels.is_empty() {
let n = rels.len();
let transformed =
ext.exchange_relation_changes(rel_batch_id, rels).await?;
if transformed.len() != n {
bail!(
"External stage changed relation-count ({n} → {}) while row \
changes were present in the same batch; use homogeneous batches \
for filter/fan-out",
transformed.len()
);
}
for (idx, r) in rel_idxs.into_iter().zip(transformed) {
events[idx] = ApplyEvent::relation_change(r);
}
}
}
}
}
}
Ok(events)
}
}