use crate::SqlRenderer;
#[derive(Debug)]
pub struct TaskRenderConfig<'a> {
pub name: &'a str,
pub body: &'a str,
pub schedule: Option<String>,
pub warehouse: Option<&'a str>,
pub comment: Option<&'a str>,
pub error_integration: Option<&'a str>,
pub condition: Option<&'a str>,
pub dependencies: Vec<TaskDependencyConfig<'a>>,
}
#[derive(Debug)]
pub struct TaskDependencyConfig<'a> {
pub predecessor: &'a str,
pub condition: &'a str,
}
#[must_use]
pub fn render_dependency_comment(deps: &[TaskDependencyConfig<'_>]) -> String {
if deps.is_empty() {
return String::new();
}
let strs: Vec<String> =
deps.iter().map(|d| format!("{} ({})", d.predecessor, d.condition)).collect();
format!("-- Dependencies: {}", strs.join(", "))
}
pub trait TaskRenderer: SqlRenderer {
fn render_schedule_cron(&self, expression: &str, timezone: Option<&str>) -> String {
let tz = timezone.unwrap_or("UTC");
format!("-- Schedule: cron {expression} {tz}")
}
fn render_schedule_interval(&self, minutes: u32) -> String {
format!("-- Schedule: every {minutes} minutes")
}
fn render_schedule_stream(&self, stream_name: &str) -> String {
format!("-- Stream trigger: {stream_name}")
}
fn render_schedule_event(&self, source: &str, description: &str) -> String {
format!("-- Event-driven: {source} ({description})")
}
fn render_create_task(&self, config: &TaskRenderConfig<'_>) -> String {
let mut lines = Vec::new();
if let Some(comment) = config.comment {
lines.push(format!("-- {comment}"));
}
if let Some(ref schedule) = config.schedule {
lines.push(schedule.clone());
}
let dep = render_dependency_comment(&config.dependencies);
if !dep.is_empty() {
lines.push(dep);
}
lines.push(format!("{};", config.body));
lines.join("\n")
}
fn render_drop_task(&self, name: &str) -> String {
format!("-- Drop task: {name} (platform manages externally)")
}
fn render_resume_task(&self, name: &str) -> String {
format!("-- Resume task: {name} (no native suspend/resume)")
}
fn render_suspend_task(&self, name: &str) -> String {
format!("-- Suspend task: {name} (no native suspend/resume)")
}
fn render_bulk_load(&self, _table: &str, _source: &str, _format: &str) -> String {
"-- Bulk load not supported on this platform".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DataType;
struct TestDialect;
impl SqlRenderer for TestDialect {
fn render_data_type(&self, _dt: &DataType) -> String {
"TEXT".to_string()
}
fn quote_identifier(&self, name: &str) -> String {
format!("\"{name}\"")
}
fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
match schema {
Some(s) => format!("\"{s}\".\"{name}\""),
None => format!("\"{name}\""),
}
}
fn render_current_timestamp(&self) -> String {
"CURRENT_TIMESTAMP".to_string()
}
}
impl TaskRenderer for TestDialect {}
#[test]
fn task_renderer_defaults_return_comments() {
let d = TestDialect;
assert_eq!(
d.render_schedule_cron("0 * * * *", Some("UTC")),
"-- Schedule: cron 0 * * * * UTC"
);
assert_eq!(d.render_schedule_interval(15), "-- Schedule: every 15 minutes");
assert_eq!(d.render_schedule_stream("my_stream"), "-- Stream trigger: my_stream");
assert_eq!(
d.render_schedule_event("sns", "order placed"),
"-- Event-driven: sns (order placed)"
);
let config = TaskRenderConfig {
name: "hourly",
body: "SELECT 1",
schedule: Some("-- Schedule: cron 0 * * * * UTC".to_string()),
warehouse: None,
comment: Some("Hourly check"),
error_integration: None,
condition: None,
dependencies: vec![],
};
let sql = d.render_create_task(&config);
assert!(sql.contains("-- Hourly check"));
assert!(sql.contains("SELECT 1;"));
assert_eq!(
d.render_drop_task("hourly"),
"-- Drop task: hourly (platform manages externally)"
);
assert_eq!(
d.render_resume_task("hourly"),
"-- Resume task: hourly (no native suspend/resume)"
);
assert_eq!(
d.render_suspend_task("hourly"),
"-- Suspend task: hourly (no native suspend/resume)"
);
assert_eq!(
d.render_bulk_load("raw_orders", "s3://bucket/orders.csv", "csv"),
"-- Bulk load not supported on this platform"
);
}
#[test]
fn render_dependency_comment_formats_correctly() {
let deps = vec![
TaskDependencyConfig { predecessor: "task_a", condition: "on success" },
TaskDependencyConfig { predecessor: "task_b", condition: "on completion" },
];
let result = render_dependency_comment(&deps);
assert_eq!(result, "-- Dependencies: task_a (on success), task_b (on completion)");
}
#[test]
fn render_dependency_comment_empty_returns_empty() {
assert_eq!(render_dependency_comment(&[]), "");
}
}