# flow — Agent & Contributor Guide

Navigation aid for `src/flow`. Keep it short and point to code. Paths are
relative to the repo root.

Repo-wide rules that apply here: [`.agents/architecture-invariants.md`](../../.agents/architecture-invariants.md).

## What this crate does

Flownode is the stream-processing engine behind continuous aggregation /
materialized views. It has two execution paths:

- **Batching mode** (the actively developed path): splits data
  into time windows and periodically runs aggregation SQL through the frontend,
  writing results back to a sink table.
- **Streaming mode** (the legacy dataflow path): an incremental DFIR/dataflow
  compute graph that processes row-level diffs.

Users cannot select a mode directly: `flow_type` is a reserved internal option.
`StatementExecutor::determine_flow_type` in `src/operator/src/statement/ddl.rs`
owns current mode selection. `FlowDualEngine` defaults missing internal
`flow_type` metadata to batching for compatibility. Read those paths before
changing routing rules.

## Module map

| Module | Path | Purpose |
| --- | --- | --- |
| `engine` | `src/flow/src/engine.rs` | `FlowEngine` trait: create/remove/flush/insert lifecycle |
| `adapter` | `src/flow/src/adapter.rs`, `src/flow/src/adapter/` | `StreamingEngine`, worker pool, dual-engine dispatch, table sources/sinks |
| `batching_mode` | `src/flow/src/batching_mode.rs`, `src/flow/src/batching_mode/` | `BatchingEngine`, task scheduling, time windows, frontend client, checkpoints |
| `compute` | `src/flow/src/compute/` | Streaming dataflow render/state |
| `expr` | `src/flow/src/expr.rs`, `src/flow/src/expr/` | Scalar/aggregate expressions and Map-Filter-Project |
| `plan` | `src/flow/src/plan.rs` | `TypedPlan` (reduce/join/MFP) |
| `transform` | `src/flow/src/transform.rs` | Substrait → flow plan |
| `df_optimizer` | `src/flow/src/df_optimizer.rs` | SQL → DataFusion logical plan → optimized plan |
| `repr` | `src/flow/src/repr.rs` | `Row`, `DiffRow`, `Batch`, `RelationDesc` |
| `server` | `src/flow/src/server.rs` | gRPC `Flow` service, `FlownodeBuilder`/`FlownodeInstance` |
| `heartbeat` | `src/flow/src/heartbeat.rs` | Reports flownode state/stats to metasrv |

Flow metadata lives in `common-meta`, not here:
`src/common/meta/src/key/flow/` and `src/common/meta/src/ddl/create_flow.rs`.

## Data flow

`Frontend → Flownode (gRPC)` → `FlowService` (`server.rs`) →
`FlowDualEngine` (`adapter/flownode_impl.rs`) routes by `FlowType`:

- Batching: marks dirty windows; a task later runs aggregation SQL via the
  frontend client and writes the sink table (`batching_mode/task.rs`).
- Streaming: worker threads apply incremental diffs and push to the sink
  (`adapter/worker.rs`, `compute/render.rs`).

## Public surface

- gRPC `Flow` service in `src/flow/src/server.rs`
  (`handle_create_remove`, `handle_mirror_request`, `handle_mark_dirty_time_window`).
- Sink writes for the streaming engine go through `FrontendInvoker`
  (`row_inserts`, `row_deletes`).
- `FlowEngine` trait in `src/flow/src/engine.rs`.
- Started from the `cmd` crate via `FlownodeBuilder` / `FlownodeInstance`.

## When you change X, also touch Y

- **Flow definition / options**: validation in `common-meta`'s `ddl/create_flow.rs`
  and the serialized `FlowInfoValue` in `common-meta`'s `key/flow/`.
- **New scalar/aggregate function** (`expr/`): also wire up evaluation in
  `compute/render.rs` (streaming) and ensure batching SQL handles it.
- **Persisted flow metadata**: keep `FlowInfoValue` backward compatible
  (`serde(default)` / `serde(alias)`).
- **Shared routing, metadata, or sink contracts**: check both engines. An
  engine-specific implementation change does not automatically need a mirror
  change in the other path.

## Testing

```bash
cargo nextest run -p flow
```

Helpers in `src/flow/src/test_utils.rs` (test context, test query engine).

## Gotchas

- Batching vs streaming differ in latency, state, and execution. Confirm the
  selected mode before reasoning about a flow.
- Streaming workers are `!Send`; cross-thread interaction goes through
  `WorkerHandle`, not the worker directly.
- Internal flow timestamps (`repr::Timestamp`, ms) are not necessarily the
  table's time column; window functions key off the diff timestamp.

## Maintenance contract

Update this file when the dual-engine routing, the gRPC surface, or the flow
metadata contract (shared with `common-meta`) changes.
