1use super::{Array, BinOp, CellError, MAX_CELLS, Value, compare};
2
3#[derive(Clone, Copy, PartialEq, Eq)]
5pub(super) enum Agg {
6 Sum,
7 Count,
8 Average,
9}
10
11impl Agg {
12 pub(super) fn of(name: &str) -> Option<Agg> {
13 match name {
14 "SUM" => Some(Agg::Sum),
15 "COUNT" => Some(Agg::Count),
16 "AVERAGE" => Some(Agg::Average),
17 _ => None,
18 }
19 }
20}
21
22#[derive(Default)]
24pub(super) struct Acc {
25 pub(super) sum: f64,
26 pub(super) count: u64,
27}
28
29impl Acc {
30 pub(super) fn add_cell(&mut self, kind: Agg, v: &Value) -> Result<(), CellError> {
33 match v {
34 Value::Number(n) => {
35 self.sum += n;
36 self.count = self.count.saturating_add(1);
37 Ok(())
38 }
39 Value::Error(e) if kind != Agg::Count => Err(*e),
40 _ => Ok(()),
41 }
42 }
43
44 pub(super) fn finish(&self, kind: Agg) -> Value {
45 match kind {
46 Agg::Sum => Value::number(self.sum),
47 Agg::Count => Value::number(self.count as f64),
48 Agg::Average if self.count == 0 => Value::Error(CellError::Div0),
49 Agg::Average => Value::number(self.sum / self.count as f64),
50 }
51 }
52}
53
54pub(super) fn map_unary(v: &Value, f: &dyn Fn(f64) -> Value, plus: bool, sys: crate::base::DateSystem) -> Value {
57 match v {
58 Value::Array(a) => Value::from(Array { rows: a.rows, cols: a.cols, data: a.data.iter().map(|x| map_unary(x, f, plus, sys)).collect() }),
59 Value::Error(e) => Value::Error(*e),
60 Value::Text(_) if plus => v.clone(),
61 other => match other.to_number_in(sys) {
62 Ok(n) => f(n),
63 Err(e) => Value::Error(e),
64 },
65 }
66}
67
68pub fn zip2(a: &Value, b: &Value, f: impl Fn(&Value, &Value) -> Value) -> Value {
70 match (a, b) {
71 (Value::Array(x), Value::Array(y)) => {
72 let rows = broadcast_dim(x.rows, y.rows);
73 let cols = broadcast_dim(x.cols, y.cols);
74 let Some(n) = broadcast_len(rows, cols) else { return Value::Error(CellError::Num) };
75 let mut data = Vec::with_capacity(n);
76 for r in 0..rows {
77 for c in 0..cols {
78 data.push(f(&x.get_broadcast(r, c), &y.get_broadcast(r, c)));
79 }
80 }
81 Value::from(Array { rows, cols, data })
82 }
83 (Value::Array(x), s) => Value::from(Array { rows: x.rows, cols: x.cols, data: x.data.iter().map(|v| f(v, s)).collect() }),
84 (s, Value::Array(y)) => Value::from(Array { rows: y.rows, cols: y.cols, data: y.data.iter().map(|v| f(s, v)).collect() }),
85 (x, y) => f(x, y),
86 }
87}
88
89fn try_zip2(a: &Value, b: &Value, mut f: impl FnMut(&Value, &Value) -> Option<Value>) -> Option<Value> {
92 Some(match (a, b) {
93 (Value::Array(x), Value::Array(y)) => {
94 let rows = broadcast_dim(x.rows, y.rows);
95 let cols = broadcast_dim(x.cols, y.cols);
96 if broadcast_len(rows, cols).is_none() {
97 return Some(Value::Error(CellError::Num));
98 }
99 let mut data = Vec::new();
100 for r in 0..rows {
101 for c in 0..cols {
102 data.push(f(&x.get_broadcast(r, c), &y.get_broadcast(r, c))?);
103 }
104 }
105 Value::from(Array { rows, cols, data })
106 }
107 (Value::Array(x), s) => Value::from(Array { rows: x.rows, cols: x.cols, data: x.data.iter().map(|v| f(v, s)).collect::<Option<_>>()? }),
108 (s, Value::Array(y)) => Value::from(Array { rows: y.rows, cols: y.cols, data: y.data.iter().map(|v| f(s, v)).collect::<Option<_>>()? }),
109 (x, y) => f(x, y)?,
110 })
111}
112
113pub(super) fn broadcast_len(rows: usize, cols: usize) -> Option<usize> {
119 let cells = u64::try_from(rows).ok()?.checked_mul(u64::try_from(cols).ok()?)?;
120 if cells > MAX_CELLS {
121 return None;
122 }
123 usize::try_from(cells).ok()
124}
125
126fn broadcast_dim(a: usize, b: usize) -> usize {
127 if a == 1 {
128 b
129 } else if b == 1 {
130 a
131 } else {
132 a.max(b)
133 }
134}
135
136pub(super) fn zip3(c: &Array, t: &Value, f: &Value, g: impl Fn(&Value, &Value, &Value) -> Value) -> Value {
137 let dims = |v: &Value| if let Value::Array(a) = v { (a.rows, a.cols) } else { (1, 1) };
138 let (tr, tc) = dims(t);
139 let (fr, fc) = dims(f);
140 let rows = broadcast_dim(broadcast_dim(c.rows, tr), fr);
141 let cols = broadcast_dim(broadcast_dim(c.cols, tc), fc);
142 let Some(n) = broadcast_len(rows, cols) else { return Value::Error(CellError::Num) };
143 let get = |v: &Value, r: usize, col: usize| if let Value::Array(a) = v { a.get_broadcast(r, col) } else { v.clone() };
144 let mut data = Vec::with_capacity(n);
145 for r in 0..rows {
146 for col in 0..cols {
147 data.push(g(&c.get_broadcast(r, col), &get(t, r, col), &get(f, r, col)));
148 }
149 }
150 Value::from(Array { rows, cols, data })
151}
152
153pub(super) fn single(v: Value) -> Option<Value> {
156 match v {
157 Value::Array(a) if a.data.len() > 1 => None,
158 v => Some(v.scalar()),
159 }
160}
161
162pub(super) const MAX_CONCAT_ARRAY_BYTES: usize = 512 * 1024 * 1024;
167
168pub fn binary_values(op: BinOp, x: &Value, y: &Value, sys: crate::base::DateSystem) -> Value {
169 if op == BinOp::Concat && (matches!(x, Value::Array(_)) || matches!(y, Value::Array(_))) {
170 return concat_arrays(x, y, sys, MAX_CONCAT_ARRAY_BYTES);
171 }
172 zip2(x, y, |a, b| binary_scalar(op, a, b, sys))
173}
174
175fn concat_arrays(x: &Value, y: &Value, sys: crate::base::DateSystem, budget: usize) -> Value {
178 let mut used = 0usize;
179 try_zip2(x, y, |a, b| {
180 let v = binary_scalar(BinOp::Concat, a, b, sys);
181 if let Value::Text(t) = &v {
182 used = used.saturating_add(t.len());
183 if used > budget {
184 return None;
185 }
186 }
187 Some(v)
188 })
189 .unwrap_or(Value::Error(CellError::Num))
190}
191
192fn binary_scalar(op: BinOp, a: &Value, b: &Value, sys: crate::base::DateSystem) -> Value {
193 if let Value::Error(e) = a {
194 return Value::Error(*e);
195 }
196 if let Value::Error(e) = b {
197 return Value::Error(*e);
198 }
199 let num = |f: fn(f64, f64) -> Value| match (a.to_number_in(sys), b.to_number_in(sys)) {
200 (Ok(x), Ok(y)) => f(x, y),
201 (Err(e), _) | (_, Err(e)) => Value::Error(e),
202 };
203 use std::cmp::Ordering::*;
204 match op {
205 BinOp::Add => num(|x, y| Value::number(x + y)),
206 BinOp::Sub => num(|x, y| Value::number(x - y)),
207 BinOp::Mul => num(|x, y| Value::number(x * y)),
208 BinOp::Div => num(|x, y| if y == 0.0 { Value::Error(CellError::Div0) } else { Value::number(x / y) }),
209 BinOp::Pow => num(|x, y| {
210 if x == 0.0 && y == 0.0 {
211 Value::Error(CellError::Num)
212 } else if x == 0.0 && y < 0.0 {
213 Value::Error(CellError::Div0)
214 } else {
215 Value::number(x.powf(y))
216 }
217 }),
218 BinOp::Concat => match (a.to_text(), b.to_text()) {
219 (Ok(x), Ok(y)) => {
220 if x.len() + y.len() > 32767 * 4 {
221 Value::Error(CellError::Value)
222 } else {
223 Value::text(x + &y)
224 }
225 }
226 (Err(e), _) | (_, Err(e)) => Value::Error(e),
227 },
228 BinOp::Eq => Value::Bool(compare(a, b) == Equal),
229 BinOp::Ne => Value::Bool(compare(a, b) != Equal),
230 BinOp::Lt => Value::Bool(compare(a, b) == Less),
231 BinOp::Gt => Value::Bool(compare(a, b) == Greater),
232 BinOp::Le => Value::Bool(compare(a, b) != Greater),
233 BinOp::Ge => Value::Bool(compare(a, b) != Less),
234 BinOp::Range | BinOp::Union | BinOp::Intersect => Value::Error(CellError::Value),
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::{Array, BinOp, CellError, MAX_CELLS, Value, broadcast_len, concat_arrays, try_zip2};
241
242 #[test]
244 fn a_refused_cell_stops_the_broadcast() {
245 let col = Value::from(Array::column((0..4_000).map(|i| Value::number(i as f64)).collect()));
246 let row = Value::from(Array { rows: 1, cols: 4_000, data: (0..4_000).map(|i| Value::number(i as f64)).collect() });
247 let mut calls = 0;
248 let r = try_zip2(&col, &row, |a, _| {
249 calls += 1;
250 (calls <= 10).then(|| a.clone())
251 });
252 assert_eq!((r, calls), (None, 11));
253 }
254
255 #[test]
256 fn concatenating_arrays_stops_at_the_byte_budget() {
257 let sys = crate::base::DateSystem::default();
258 let col = Value::from(Array::column((0..100).map(|i| Value::number(i as f64)).collect()));
259 let long = Value::text("x".repeat(100));
260 let r = concat_arrays(&col, &long, sys, 20_000);
262 assert!(matches!(&r, Value::Array(a) if a.data.len() == 100), "{r:?}");
263 assert_eq!(concat_arrays(&col, &long, sys, 5_000), Value::Error(CellError::Num));
264 assert_eq!(concat_arrays(&long, &col, sys, 5_000), Value::Error(CellError::Num));
265 let r = super::binary_values(BinOp::Concat, &col, &Value::text("a"), sys);
267 assert!(matches!(&r, Value::Array(a) if a.data.first() == Some(&Value::text("0a"))), "{r:?}");
268 }
269
270 #[test]
271 fn broadcast_len_caps_at_max_cells() {
272 assert_eq!(broadcast_len(4_000, 4_000), Some(MAX_CELLS as usize));
273 assert_eq!(broadcast_len(4_000, 4_001), None);
274 assert_eq!(broadcast_len(1, MAX_CELLS as usize), Some(MAX_CELLS as usize));
275 }
276
277 #[test]
280 fn broadcast_len_rejects_a_product_that_wraps_to_zero_on_32_bits() {
281 assert_eq!(65_536u32.wrapping_mul(65_536), 0);
282 assert_eq!(broadcast_len(65_536, 65_536), None);
283 assert_eq!(broadcast_len(1_048_576, 16_384), None);
284 }
285}