use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SqlTransformConfig {
pub query: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub relations: Vec<RelationSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub memory_limit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub threads: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct RelationSpec {
pub name: String,
pub source: RelationSource,
#[serde(default)]
pub reload_on_change: bool,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum RelationSource {
Csv {
path: String,
#[serde(default = "default_true")]
has_header: bool,
},
Jsonl {
path: String,
},
Values {
columns: Vec<String>,
rows: Vec<Vec<Value>>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_round_trips_and_schema_builds() {
let cfg: SqlTransformConfig = serde_json::from_value(serde_json::json!({
"query": "SELECT * FROM batch",
"relations": [
{"name": "countries",
"source": {"type": "csv", "path": "c.csv", "has_header": true}}
]
}))
.unwrap();
assert_eq!(cfg.relations.len(), 1);
assert!(matches!(
cfg.relations[0].source,
RelationSource::Csv { .. }
));
let schema = schemars::schema_for!(SqlTransformConfig);
let json = serde_json::to_value(&schema).unwrap();
assert!(
json.get("properties")
.and_then(|p| p.get("query"))
.is_some()
);
}
}