#![allow(clippy::print_stdout, clippy::print_stderr)]
use serde::{Deserialize, Serialize};
use spate::avro::AvroDeserializerBuilder;
use spate::clickhouse::{DateTime64Millis, NativeEncoder, ShardKey};
use spate::kafka::KafkaSource;
use spate::prelude::*;
use std::path::Path;
#[derive(Debug, Deserialize)]
struct MetricBatch {
host: String,
ts_ms: i64,
readings: Vec<Reading>,
}
#[derive(Debug, Deserialize)]
struct Reading {
kind: String,
name: String,
value: i64,
text: String,
}
#[derive(Debug)]
struct Sample {
host: String,
ts_ms: i64,
kind: String,
name: String,
value: i64,
text: String,
}
#[derive(Debug, Serialize)]
struct GaugeRow {
host: String,
ts_ms: DateTime64Millis,
name: String,
value: i64,
}
#[derive(Debug, Serialize)]
struct TextRow {
host: String,
ts_ms: DateTime64Millis,
name: String,
text: String,
}
fn gauge_key(row: &GaugeRow) -> ShardKey<'_> {
ShardKey::Str(&row.host)
}
fn text_key(row: &TextRow) -> ShardKey<'_> {
ShardKey::Str(&row.host)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config_path = std::env::var("SPATE_CONFIG")
.unwrap_or_else(|_| "crates/spate/examples/multi_table_split.yaml".to_string());
let pipeline = Pipeline::from_path(Path::new(&config_path))?;
let source = KafkaSource::from_component_config(&pipeline.config().source)?;
let deser_section = pipeline
.config()
.deserializer
.as_ref()
.ok_or("this pipeline requires a `deserializer` section")?;
let deserializer =
AvroDeserializerBuilder::from_component(deser_section, &pipeline.io_handle())?
.build_serde::<MetricBatch>()?;
let gauge_sink =
spate::clickhouse::config::from_component_config(pipeline.config().sink_config("gauge")?)?;
let text_sink =
spate::clickhouse::config::from_component_config(pipeline.config().sink_config("text")?)?;
let gauge_router = gauge_sink.router::<Owned<GaugeRow>>(gauge_key);
let text_router = text_sink.router::<Owned<TextRow>>(text_key);
let gauge_enc =
NativeEncoder::<Owned<GaugeRow>>::new(pipeline.block_on(gauge_sink.native_schema())?);
let text_enc =
NativeEncoder::<Owned<TextRow>>::new(pipeline.block_on(text_sink.native_schema())?);
let report = pipeline
.add_sink("gauge", gauge_sink)?
.add_sink("text", text_sink)?
.chains(move |ctx| {
let mut split = chain::<Owned<MetricBatch>, _>(deserializer.clone())
.with_metrics(ctx.pipeline.clone(), "main")
.flat_map::<Owned<Sample>, _>(|batch, out| {
let (host, ts_ms) = (batch.host, batch.ts_ms);
for r in batch.readings {
out.emit(Sample {
host: host.clone(),
ts_ms,
kind: r.kind,
name: r.name,
value: r.value,
text: r.text,
});
}
})
.split(ErrorPolicy::Skip);
let gauge = split.add::<Owned<GaugeRow>, _, _>(
gauge_enc.clone(),
gauge_router.clone(),
ctx.sink("gauge"),
);
let text = split.add::<Owned<TextRow>, _, _>(
text_enc.clone(),
text_router.clone(),
ctx.sink("text"),
);
split
.route(move |s: Sample, out| match s.kind.as_str() {
"gauge" => out.emit(
gauge,
GaugeRow {
host: s.host,
ts_ms: DateTime64Millis(s.ts_ms),
name: s.name,
value: s.value,
},
),
"text" => out.emit(
text,
TextRow {
host: s.host,
ts_ms: DateTime64Millis(s.ts_ms),
name: s.name,
text: s.text,
},
),
_ => {}
})
.build()
})
.run(source)?;
report.log();
std::process::exit(report.exit_code());
}