1use std::sync::Arc;
33
34use datafusion::arrow::array::{
35 ArrayRef, BooleanArray, Float32Array, Float64Array, Int32Array, Int64Array, RecordBatch, StringArray,
36};
37use fv_compute::{FvType, Runtime, SchemaSpec};
38use fv_value::Value;
39use serde_json::Value as J;
40
41use crate::build::ComputeRunner;
42use crate::convert::batch_to_rows;
43use crate::row::Row;
44
45#[derive(Clone)]
47pub struct KineticsRunner {
48 runtime: Arc<Runtime>,
49}
50
51impl KineticsRunner {
52 pub fn new(runtime: Arc<Runtime>) -> Self {
53 Self { runtime }
54 }
55
56 pub fn runtime(&self) -> &Arc<Runtime> {
58 &self.runtime
59 }
60
61 pub fn selector(step: &J) -> Result<&str, String> {
63 step["ref"]
64 .as_str()
65 .or_else(|| step["transform"].as_str())
66 .ok_or_else(|| "compute step missing `ref` (the transform id or id@version)".to_string())
67 }
68
69 pub fn ensure_loadable(&self, step: &J) -> Result<(), String> {
72 self.runtime
73 .ensure_loadable(Self::selector(step)?)
74 .map_err(|e| e.to_string())
75 }
76
77 pub fn run_sync(&self, step: &J, inputs: &[(String, Vec<Row>)]) -> Result<Vec<Row>, String> {
81 let selector = Self::selector(step)?;
82 let compute = self.runtime.load(selector).map_err(|e| e.to_string())?;
83 let manifest = compute.manifest();
84 if !manifest.inputs.is_empty() && manifest.inputs.len() != inputs.len() {
85 return Err(format!(
86 "transform `{selector}` declares {} input(s), the pipeline supplied {}",
87 manifest.inputs.len(),
88 inputs.len()
89 ));
90 }
91 let batches: Vec<RecordBatch> = manifest
92 .inputs
93 .iter()
94 .zip(inputs.iter())
95 .map(|(signature, (_, rows))| rows_to_typed_batch(signature, rows))
96 .collect::<Result<_, _>>()?;
97 let out = compute.run(&batches).map_err(|e| e.to_string())?;
98 Ok(batch_to_rows(&out))
99 }
100}
101
102#[async_trait::async_trait]
103impl ComputeRunner for KineticsRunner {
104 async fn run(&self, step: &J, inputs: &[(String, Vec<Row>)]) -> Result<Vec<Row>, String> {
105 self.run_sync(step, inputs)
106 }
107}
108
109pub fn rows_to_typed_batch(signature: &SchemaSpec, rows: &[Row]) -> Result<RecordBatch, String> {
113 let arrays: Vec<ArrayRef> = signature
114 .columns
115 .iter()
116 .map(|c| typed_column(c.dtype, &c.name, rows))
117 .collect::<Result<_, _>>()?;
118 RecordBatch::try_new(signature.to_arrow_schema_ref(), arrays).map_err(|e| e.to_string())
119}
120
121fn number(v: &Value) -> Option<f64> {
122 match v {
123 Value::Num(n) => Some(*n),
124 _ => None,
125 }
126}
127
128fn text(v: &Value) -> Option<String> {
129 match v {
130 Value::Null => None,
131 Value::Str(s) => Some(s.clone()),
132 Value::Bool(b) => Some(if *b { "true" } else { "false" }.into()),
133 Value::Num(n) => Some(if n.fract() == 0.0 && n.is_finite() {
134 format!("{}", *n as i64)
135 } else {
136 format!("{n}")
137 }),
138 other => Some(format!("{other:?}")),
139 }
140}
141
142fn typed_column(dtype: FvType, name: &str, rows: &[Row]) -> Result<ArrayRef, String> {
143 let cell = |r: &Row| r.get(name);
144 Ok(match dtype {
145 FvType::Int64 => Arc::new(
146 rows.iter()
147 .map(|r| number(&cell(r)).map(|n| n as i64))
148 .collect::<Int64Array>(),
149 ),
150 FvType::Int32 => Arc::new(
151 rows.iter()
152 .map(|r| number(&cell(r)).map(|n| n as i32))
153 .collect::<Int32Array>(),
154 ),
155 FvType::Float64 => Arc::new(rows.iter().map(|r| number(&cell(r))).collect::<Float64Array>()),
156 FvType::Float32 => Arc::new(
157 rows.iter()
158 .map(|r| number(&cell(r)).map(|n| n as f32))
159 .collect::<Float32Array>(),
160 ),
161 FvType::Bool => Arc::new(
162 rows.iter()
163 .map(|r| match cell(r) {
164 Value::Bool(b) => Some(b),
165 _ => None,
166 })
167 .collect::<BooleanArray>(),
168 ),
169 FvType::Utf8 => Arc::new(rows.iter().map(|r| text(&cell(r))).collect::<StringArray>()),
170 other => {
171 return Err(format!(
172 "compute step: column `{name}` has type {other:?}, which the row bridge does not carry yet"
173 ))
174 }
175 })
176}