fv_compute/contract.rs
1//! The in-engine dispatch seam. Every backend (builtin, expression, wasm
2//! container, llm) implements [`TransformBackend`]; the standalone
3//! `fv-compute` library is what the pipeline runner, the derived-property materializer, the
4//! Action runtime, or a REPL embeds to run `transform X@v over this Arrow batch`.
5//!
6//! Scalable-pipeline shape (the whole point): the seam is **batch-vectorized**
7//! (whole `RecordBatch`es, never row-at-a-time) and **`Send + Sync`**, so DataFusion can call a
8//! loaded [`Compute`] per partition across all cores; a backend that has per-instance cost (the
9//! wasm `Store` pool) amortizes it behind [`TransformBackend::load`], which is invoked once and
10//! whose [`Compute`] is then called per batch.
11
12use crate::manifest::{ImplKind, TransformManifest};
13use arrow::array::RecordBatch;
14use std::path::Path;
15
16#[derive(Debug, thiserror::Error)]
17pub enum ComputeError {
18 /// The backend could not load/compile the transform (missing artifact, bad module, …).
19 #[error("failed to load transform `{id}`: {msg}")]
20 Load { id: String, msg: String },
21 /// The input batch(es) did not match the declared signature.
22 #[error("schema mismatch in `{id}`: {msg}")]
23 Schema { id: String, msg: String },
24 /// The transform itself failed (guest error, timeout, fail-closed).
25 #[error("transform `{id}` failed: {msg}")]
26 Run { id: String, msg: String },
27 /// No backend is registered for this impl kind.
28 #[error("no backend registered for impl `{kind:?}`")]
29 NoBackend { kind: ImplKind },
30}
31
32/// A loaded, ready-to-invoke transform. Cheap to call per batch (any expensive setup happened in
33/// [`TransformBackend::load`]). `Send + Sync` so it can be shared across partition workers.
34pub trait Compute: Send + Sync {
35 /// The manifest this compute was loaded from.
36 fn manifest(&self) -> &TransformManifest;
37
38 /// Transform one set of input batches into one output batch (batch-vectorized). The number of
39 /// input batches matches `manifest().inputs`. Returns [`ComputeError`] to fail closed —
40 /// governance wraps this; the compute never touches the write path.
41 fn run(&self, inputs: &[RecordBatch]) -> Result<RecordBatch, ComputeError>;
42}
43
44/// A pluggable backend for one [`ImplKind`]. Implemented by the wasm/container/expression/builtin
45/// backends. `load` does the expensive work once (compile the wasm module, parse
46/// the expression, resolve the builtin); the returned [`Compute`] is the hot per-batch path.
47pub trait TransformBackend: Send + Sync {
48 /// Which impl kind this backend serves.
49 fn kind(&self) -> ImplKind;
50
51 /// Load a transform from its (validated) manifest and directory root into a callable [`Compute`].
52 fn load(&self, manifest: &TransformManifest, root: &Path) -> Result<Box<dyn Compute>, ComputeError>;
53}