use crate::pipeline::apply::ApplyEvent;
use crate::pipeline::pipeline::Pipeline;
use anyhow::{bail, Result};
use async_trait::async_trait;
use surreal_sync_core::{Change, Relation, RelationChange, Row};
#[async_trait]
pub trait BatchTransformer: Send + Sync {
fn is_identity(&self) -> bool;
async fn transform_changes(&self, batch_id: u64, changes: Vec<Change>) -> Result<Vec<Change>>;
async fn transform_rows(&self, batch_id: u64, rows: Vec<Row>) -> Result<Vec<Row>>;
async fn transform_relation_changes(
&self,
_batch_id: u64,
_changes: Vec<RelationChange>,
) -> Result<Vec<RelationChange>> {
bail!(
"BatchTransformer::transform_relation_changes is not implemented; \
override it (or return Ok(changes) for explicit passthrough), or use Pipeline"
)
}
async fn transform_relations(
&self,
_batch_id: u64,
_relations: Vec<Relation>,
) -> Result<Vec<Relation>> {
bail!(
"BatchTransformer::transform_relations is not implemented; \
override it (or return Ok(relations) for explicit passthrough), or use Pipeline"
)
}
async fn transform_events(
&self,
batch_id: u64,
events: Vec<ApplyEvent>,
) -> Result<Vec<ApplyEvent>> {
let mut change_idxs = Vec::new();
let mut changes = Vec::new();
let mut rel_idxs = Vec::new();
let mut rels = Vec::new();
let mut out: Vec<Option<ApplyEvent>> = Vec::with_capacity(events.len());
for (i, event) in events.into_iter().enumerate() {
out.push(None);
match event {
ApplyEvent::Change(c) => {
change_idxs.push(i);
changes.push(c);
}
ApplyEvent::RelationChange(r) => {
rel_idxs.push(i);
rels.push(*r);
}
}
}
if !changes.is_empty() {
let n = changes.len();
let transformed = self.transform_changes(batch_id, changes).await?;
if transformed.len() != n {
bail!(
"transform_changes changed length ({n} → {}) in a mixed ApplyEvent batch; \
use homogeneous batches for filter/fan-out",
transformed.len()
);
}
for (idx, c) in change_idxs.into_iter().zip(transformed) {
out[idx] = Some(ApplyEvent::Change(c));
}
}
if !rels.is_empty() {
let n = rels.len();
let transformed = self.transform_relation_changes(batch_id, rels).await?;
if transformed.len() != n {
bail!(
"transform_relation_changes changed length ({n} → {}) in a mixed \
ApplyEvent batch; use homogeneous batches for filter/fan-out",
transformed.len()
);
}
for (idx, r) in rel_idxs.into_iter().zip(transformed) {
out[idx] = Some(ApplyEvent::relation_change(r));
}
}
Ok(out
.into_iter()
.map(|e| e.expect("every slot filled"))
.collect())
}
}
#[async_trait]
impl BatchTransformer for Pipeline {
fn is_identity(&self) -> bool {
Pipeline::is_identity(self)
}
async fn transform_changes(&self, batch_id: u64, changes: Vec<Change>) -> Result<Vec<Change>> {
self.apply_changes_async(batch_id, changes).await
}
async fn transform_rows(&self, batch_id: u64, rows: Vec<Row>) -> Result<Vec<Row>> {
self.apply_rows_async(batch_id, rows).await
}
async fn transform_relation_changes(
&self,
batch_id: u64,
changes: Vec<RelationChange>,
) -> Result<Vec<RelationChange>> {
self.apply_relation_changes_async(batch_id, changes).await
}
async fn transform_relations(
&self,
batch_id: u64,
relations: Vec<Relation>,
) -> Result<Vec<Relation>> {
self.apply_relations_async(batch_id, relations).await
}
async fn transform_events(
&self,
batch_id: u64,
events: Vec<ApplyEvent>,
) -> Result<Vec<ApplyEvent>> {
self.apply_events_async(batch_id, events).await
}
}