use std::path::PathBuf;
use std::sync::Arc;
use crate::env::stream_env::StreamEnv;
use crate::runtime::chain_job::{ChainJob, ChainOperatorKind};
use crate::state::open_job_state_db;
use tinyflow_api::error::{StreamError, StreamResult};
use tinyflow_api::state::{StateStore, in_memory_job_state};
pub(crate) async fn resolve_job_state_for_execute(
env: &StreamEnv,
) -> StreamResult<(Arc<dyn StateStore>, Option<PathBuf>)> {
let needs_persistent = env.checkpoint_interval_ms > 0 || env.enable_chain_state;
if needs_persistent {
let dir = env.state_work_dir.as_ref().ok_or_else(|| StreamError::TaskFailed {
task_id: 0,
reason: "set_state_work_dir required when checkpoint_interval_ms > 0 or enable_chain_state is true".into(),
})?;
Ok((open_job_state_db(dir).await?, Some(dir.clone())))
} else {
Ok((in_memory_job_state(), env.state_work_dir.clone()))
}
}
pub(crate) fn resolve_step_name(kind: ChainOperatorKind, user_name: &Option<String>) -> String {
user_name.clone().unwrap_or_else(|| match kind {
ChainOperatorKind::Source => "Source".into(),
ChainOperatorKind::Map => "Map".into(),
ChainOperatorKind::Filter => "Filter".into(),
ChainOperatorKind::FlatMap => "FlatMap".into(),
ChainOperatorKind::Sink => "Sink".into(),
})
}
pub(crate) fn validate_chain_job(env: &StreamEnv, job: &ChainJob) -> StreamResult<()> {
if job.steps.is_empty() {
return Err(StreamError::TaskFailed {
task_id: 0,
reason: "ChainJob has no steps".into(),
});
}
if job.steps[0].kind != ChainOperatorKind::Source {
return Err(StreamError::TaskFailed {
task_id: 0,
reason: "ChainJob must start with Source".into(),
});
}
let expected_par = env.default_parallelism.max(1);
if job.parallelism != expected_par {
return Err(StreamError::TaskFailed {
task_id: 0,
reason: format!(
"ChainJob parallelism {} != env.set_parallelism({})",
job.parallelism, expected_par
),
});
}
if env.checkpoint_interval_ms > 0 {
let tail = job.steps.last().expect("non-empty chain");
if tail.kind != ChainOperatorKind::Sink {
return Err(StreamError::TaskFailed {
task_id: 0,
reason: "checkpoint enabled requires terminal Sink step (DataStream::sink or add_iceberg_sink)".into(),
});
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::chain_job::ChainOperatorKind;
#[test]
fn resolve_step_name_uses_pascal_case_defaults() {
assert_eq!(resolve_step_name(ChainOperatorKind::Map, &None), "Map");
assert_eq!(
resolve_step_name(ChainOperatorKind::Filter, &None),
"Filter"
);
assert_eq!(
resolve_step_name(ChainOperatorKind::Source, &None),
"Source"
);
}
#[test]
fn resolve_step_name_prefers_user_name() {
assert_eq!(
resolve_step_name(ChainOperatorKind::Map, &Some("my_map".into())),
"my_map"
);
}
}