1#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
20#![cfg_attr(coverage_nightly, allow(unused_features))]
23
24pub mod builtins;
25pub mod compiler;
26pub mod dataflow;
27pub mod external;
28mod hosteval;
29pub mod plan;
30pub mod reduce;
31pub mod scalar;
32pub mod session;
33pub mod typed;
34
35use improv_core_model::{CategoryId, Coordinate, ItemId, Value};
36
37pub type CoordKey = Vec<(u32, u32)>;
39
40#[derive(
47 Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
48)]
49pub enum CellValue {
50 Num(u64),
53 Bool(bool),
54 Text(String),
55 Date(i64),
59 Err(u8),
62}
63
64impl CellValue {
65 pub fn num(n: f64) -> Self {
67 CellValue::Num(n.to_bits())
68 }
69 pub fn as_num(&self) -> Option<f64> {
71 match self {
72 CellValue::Num(b) => Some(f64::from_bits(*b)),
73 _ => None,
74 }
75 }
76 pub fn from_bool_numeric(b: bool) -> Self {
79 CellValue::num(if b { 1.0 } else { 0.0 })
80 }
81 pub fn from_model_value(v: &Value) -> Option<CellValue> {
83 match v {
84 Value::Number(n) => Some(CellValue::num(*n)),
85 Value::Boolean(b) => Some(CellValue::Bool(*b)),
86 Value::Text(t) => Some(CellValue::Text(t.clone())),
87 Value::Enum(e) => Some(CellValue::num(*e as f64)),
88 Value::DateTime(dt) => Some(CellValue::Date(dt.timestamp_millis())),
89 Value::Error(e) => Some(CellValue::Err(e.kind.error_type().unwrap_or(0))),
90 }
91 }
92}
93
94impl std::fmt::Display for CellValue {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match self {
97 CellValue::Num(bits) => f.write_str(&display_number(f64::from_bits(*bits))),
98 CellValue::Bool(b) => write!(f, "{b}"),
99 CellValue::Text(t) => write!(f, "{t}"),
100 CellValue::Date(ms) => {
101 match chrono::DateTime::<chrono::Utc>::from_timestamp_millis(*ms) {
103 Some(dt) => write!(f, "{}", dt.to_rfc3339()),
104 None => write!(f, "date({ms})"),
105 }
106 }
107 CellValue::Err(c) => f.write_str(
108 improv_core_model::ValueErrorKind::from_error_type(*c).map_or("#ERR", |k| k.code()),
109 ),
110 }
111 }
112}
113
114pub fn display_number(n: f64) -> String {
119 if !n.is_finite() || n == 0.0 {
120 return if n == 0.0 { "0".into() } else { n.to_string() };
121 }
122 let rounded: f64 = format!("{n:.14e}").parse().unwrap_or(n);
125 rounded.to_string()
126}
127
128pub fn encode_coord(c: &Coordinate) -> CoordKey {
130 c.dims.iter().map(|(cat, it)| (cat.0, it.0)).collect()
131}
132
133pub fn decode_coord(k: &CoordKey) -> Coordinate {
135 Coordinate::from_pairs(k.iter().map(|(c, i)| (CategoryId(*c), ItemId(*i))))
136}
137
138pub fn project_key(k: &CoordKey, keep: &[u32]) -> CoordKey {
142 improv_verified::keys::project(k, keep)
143}
144
145#[cfg(test)]
146#[cfg_attr(coverage_nightly, coverage(off))]
147mod spike {
148 use super::*;
152 use differential_dataflow::input::InputSession;
153 use differential_dataflow::operators::{Join, Reduce};
154 use std::sync::{Arc, Mutex};
155
156 const TIME: u32 = 1;
157 const PRODUCT: u32 = 2;
158
159 fn ck(pairs: &[(u32, u32)]) -> CoordKey {
160 let c = Coordinate::from_pairs(
161 pairs
162 .iter()
163 .map(|(cat, it)| (CategoryId(*cat), ItemId(*it))),
164 );
165 encode_coord(&c)
166 }
167
168 #[test]
172 fn dd_revenue_is_incremental() {
173 let out: Arc<Mutex<Vec<(u64, CoordKey, f64)>>> = Arc::new(Mutex::new(Vec::new()));
174 let out2 = out.clone();
175
176 timely::execute::execute_directly(move |worker| {
177 let mut price_in: InputSession<u64, (CoordKey, u64), isize> = InputSession::new();
178 let mut qty_in: InputSession<u64, (CoordKey, u64), isize> = InputSession::new();
179 let probe_out = out2.clone();
180
181 worker.dataflow(|scope| {
182 let price = price_in.to_collection(scope);
183 let qty = qty_in.to_collection(scope);
184
185 let price_by_product = price.map(|(k, v)| (project_key(&k, &[PRODUCT]), v));
188 let qty_by_product = qty.map(|(k, v)| (project_key(&k, &[PRODUCT]), (k, v)));
189
190 let revenue = qty_by_product
191 .join(&price_by_product)
192 .map(|(_p, ((full, qbits), pbits))| {
193 let rev = f64::from_bits(pbits) * f64::from_bits(qbits);
194 (full, rev.to_bits())
195 })
196 .reduce(|_key, input, output| {
197 for (bits, mult) in input {
198 if *mult > 0 {
199 output.push((**bits, 1isize));
200 }
201 }
202 });
203
204 revenue.inspect(move |((k, bits), time, _diff)| {
205 probe_out
206 .lock()
207 .unwrap()
208 .push((*time, k.clone(), f64::from_bits(*bits)));
209 });
210 });
211
212 price_in.advance_to(0);
214 price_in.insert((ck(&[(PRODUCT, 20)]), 10.0f64.to_bits())); price_in.insert((ck(&[(PRODUCT, 21)]), 20.0f64.to_bits())); qty_in.advance_to(0);
217 qty_in.insert((ck(&[(TIME, 10), (PRODUCT, 20)]), 100.0f64.to_bits()));
218 qty_in.insert((ck(&[(TIME, 10), (PRODUCT, 21)]), 50.0f64.to_bits()));
219 price_in.advance_to(1);
220 qty_in.advance_to(1);
221 price_in.flush();
222 qty_in.flush();
223 while price_in.time() < &1 || qty_in.time() < &1 {
224 worker.step();
225 }
226
227 qty_in.remove((ck(&[(TIME, 10), (PRODUCT, 20)]), 100.0f64.to_bits()));
229 qty_in.insert((ck(&[(TIME, 10), (PRODUCT, 20)]), 120.0f64.to_bits()));
230 price_in.advance_to(2);
231 qty_in.advance_to(2);
232 price_in.flush();
233 qty_in.flush();
234 while qty_in.time() < &2 {
235 worker.step();
236 }
237 });
238
239 let events = out.lock().unwrap();
240 let a = ck(&[(TIME, 10), (PRODUCT, 20)]);
241 let b = ck(&[(TIME, 10), (PRODUCT, 21)]);
242
243 assert_eq!(final_value(&events, &b), 1000.0, "B = 20*50, unchanged");
244 assert_eq!(final_value(&events, &a), 1200.0, "A updated to 10*120");
245
246 let b_recomputed = events.iter().any(|(t, k, _)| *t >= 2 && k == &b);
248 assert!(!b_recomputed, "unaffected cell B must not recompute");
249 }
250
251 fn final_value(events: &[(u64, CoordKey, f64)], target: &CoordKey) -> f64 {
252 events
253 .iter()
254 .rfind(|(_, k, _)| k == target)
255 .map(|(_, _, v)| *v)
256 .unwrap_or(0.0)
257 }
258
259 #[test]
260 fn date_value_enters_the_dd_lane() {
261 let dt = chrono::DateTime::<chrono::Utc>::from_timestamp(1_700_000_000, 0).unwrap();
264 let cv = CellValue::from_model_value(&Value::DateTime(dt)).expect("date -> cell");
265 assert_eq!(cv, CellValue::Date(1_700_000_000_000));
266 assert!(format!("{cv}").starts_with("2023-11-"));
267 }
268}
269
270#[cfg(test)]
271#[cfg_attr(coverage_nightly, coverage(off))]
272mod tests {
273 #[test]
274 fn display_number_hides_binary_noise_not_digits() {
275 use super::display_number as d;
276 assert_eq!(d(1173.0 * 0.05), "58.65");
277 assert_eq!(d(936.0 * 0.05), "46.8");
278 assert_eq!(d(0.1 + 0.2), "0.3");
279 assert_eq!(d(1000.0), "1000");
280 assert_eq!(d(-2.5), "-2.5");
281 assert_eq!(d(0.0), "0");
282 assert_eq!(d(-0.0), "0");
283 assert_eq!(d(1.0 / 3.0), "0.333333333333333");
284 assert_eq!(d(123_456_789_012_345.0), "123456789012345");
285 assert_eq!(d(1e300), 1e300.to_string(), "no exponent form, as before");
286 assert_eq!(d(1.5e-7), "0.00000015");
287 assert_eq!(d(f64::NAN), "NaN");
288 assert_eq!(d(f64::INFINITY), "inf");
289 assert_eq!(super::CellValue::num(1173.0 * 0.05).to_string(), "58.65");
290 }
291
292 use super::*;
293 use improv_core_model::{ValueError, ValueErrorKind};
294
295 #[test]
296 fn cell_value_conversions_and_display() {
297 assert_eq!(CellValue::from_bool_numeric(true), CellValue::num(1.0));
298 assert_eq!(CellValue::from_bool_numeric(false), CellValue::num(0.0));
299 assert_eq!(CellValue::Bool(true).as_num(), None);
300 let cv = |v: Value| CellValue::from_model_value(&v).unwrap();
301 assert_eq!(cv(Value::Boolean(true)), CellValue::Bool(true));
302 assert_eq!(cv(Value::Text("t".into())), CellValue::Text("t".into()));
303 assert_eq!(cv(Value::Enum(3)), CellValue::num(3.0));
304 assert_eq!(
305 cv(Value::Error(ValueError::new(ValueErrorKind::MissingInput))),
306 CellValue::Err(0)
307 );
308 let shown: Vec<String> = [
309 CellValue::num(1.5),
310 CellValue::Bool(false),
311 CellValue::Text("hi".into()),
312 CellValue::Date(0),
313 CellValue::Date(i64::MAX),
314 CellValue::Err(1),
315 ]
316 .iter()
317 .map(ToString::to_string)
318 .collect();
319 assert_eq!(
320 shown[..4],
321 ["1.5", "false", "hi", "1970-01-01T00:00:00+00:00"]
322 );
323 assert_eq!(shown[4], format!("date({})", i64::MAX));
324 assert_eq!(shown[5], "#NULL!");
325 assert_eq!(CellValue::Err(0).to_string(), "#ERR");
326 assert_eq!(CellValue::Err(2).to_string(), "#DIV/0!");
327 assert_eq!(
328 cv(Value::Error(ValueError::new(ValueErrorKind::Na))),
329 CellValue::Err(7)
330 );
331 }
332
333 #[test]
334 fn coord_keys_round_trip_and_project() {
335 let c = Coordinate::from_pairs([(CategoryId(2), ItemId(20)), (CategoryId(1), ItemId(10))]);
336 let k = encode_coord(&c);
337 assert_eq!(k, vec![(1, 10), (2, 20)]);
338 assert_eq!(decode_coord(&k), c);
339 assert_eq!(project_key(&k, &[2]), vec![(2, 20)]);
340 }
341}