1use std::fmt::Display;
8
9use base64::Engine as _;
10use base64::engine::general_purpose::STANDARD;
11use prost::Message as _;
12use vortex_array::expr::proto::ExprSerializeProtoExt as _;
13use vortex_array::expr::{Expression as VortexExpression, nested_case_when};
14use vortex_array::proto::expr::Expr;
15use vortex_array::scalar_fn::{ForeignScalarFnVTable, ScalarFnId};
16use vortex_error::{VortexResult, vortex_ensure};
17use vortex_session::VortexSession;
18
19pub use super::api::{Expression, NamedExpression};
20use super::error::ScandalError;
21
22const MAX_NODES: usize = 4096;
23const MAX_DEPTH: usize = 64;
24const MAX_METADATA_BYTES: usize = 256 * 1024;
25const MAX_ENCODED_BYTES: usize = 512 * 1024;
26
27pub fn case_when(
31 branches: Vec<(VortexExpression, VortexExpression)>,
32 otherwise: Option<VortexExpression>,
33) -> VortexResult<VortexExpression> {
34 vortex_ensure!(!branches.is_empty(), "CASE needs at least one WHEN branch");
35 vortex_ensure!(
36 branches.len() <= (u32::MAX / 2) as usize,
37 "CASE has too many branches"
38 );
39 Ok(nested_case_when(branches, otherwise))
40}
41
42pub fn spql_call(
47 id: &str,
48 arguments: Vec<VortexExpression>,
49) -> Result<VortexExpression, ScandalError> {
50 let call =
51 ForeignScalarFnVTable::make_scalar_fn(ScalarFnId::new(id), Vec::new(), arguments.len());
52 VortexExpression::try_new(call, arguments).map_err(invalid)
53}
54
55impl Expression {
56 pub fn call(id: impl Into<String>, arguments: Vec<Self>) -> Result<Self, ScandalError> {
61 let children = arguments
62 .iter()
63 .map(Self::proto)
64 .collect::<Result<Vec<_>, _>>()?;
65 Self::from_proto(Expr {
66 id: id.into(),
67 metadata: Some(Vec::new()),
68 children,
69 })
70 }
71
72 pub fn and(&self, other: &Self) -> Result<Self, ScandalError> {
74 let mut conjunction =
75 vortex_array::expr::and(vortex_array::expr::lit(true), vortex_array::expr::lit(true))
76 .serialize_proto()
77 .map_err(invalid)?;
78 conjunction.children = vec![self.proto()?, other.proto()?];
79 Self::from_proto(conjunction)
80 }
81
82 pub fn new(expression: VortexExpression) -> Result<Self, ScandalError> {
84 let mut pending = vec![(&expression, 1usize)];
85 let mut nodes = 0;
86 while let Some((node, depth)) = pending.pop() {
87 nodes += 1;
88 validate_shape(nodes, pending.len(), node.children().len(), depth)?;
89 pending.extend(node.children().iter().map(|child| (child, depth + 1)));
90 }
91 let proto = expression.serialize_proto().map_err(invalid)?;
92 Self::from_proto(proto)
93 }
94
95 pub fn to_vortex(&self, session: &VortexSession) -> Result<VortexExpression, ScandalError> {
101 VortexExpression::from_proto(&self.proto()?, session).map_err(invalid)
102 }
103
104 pub fn to_bytes(&self) -> Result<Vec<u8>, ScandalError> {
106 Ok(self.proto()?.encode_to_vec())
107 }
108
109 pub fn encoded_len(&self) -> usize {
111 let padding = self
112 .0
113 .bytes()
114 .rev()
115 .take_while(|byte| *byte == b'=')
116 .count();
117 (self.0.len() / 4 * 3).saturating_sub(padding)
118 }
119
120 pub fn from_bytes(bytes: &[u8]) -> Result<Self, ScandalError> {
122 if bytes.len() > MAX_ENCODED_BYTES {
123 return Err(invalid("expression protobuf exceeds 512 KiB"));
124 }
125 Self::from_proto(Expr::decode(bytes).map_err(invalid)?)
126 }
127
128 fn proto(&self) -> Result<Expr, ScandalError> {
130 if self.0.len() > MAX_ENCODED_BYTES.div_ceil(3) * 4 {
131 return Err(invalid("expression protobuf exceeds 512 KiB"));
132 }
133 let bytes = STANDARD.decode(&self.0).map_err(invalid)?;
134 let proto = Expr::decode(bytes.as_slice()).map_err(invalid)?;
135 check_bounds(&proto)?;
136 Ok(proto)
137 }
138
139 fn from_proto(proto: Expr) -> Result<Self, ScandalError> {
140 check_bounds(&proto)?;
141 Ok(Self(STANDARD.encode(proto.encode_to_vec())))
142 }
143}
144
145impl NamedExpression {
146 pub fn new(name: impl Into<String>, expression: Expression) -> Self {
148 Self {
149 name: name.into(),
150 expression,
151 }
152 }
153}
154
155fn check_bounds(proto: &Expr) -> Result<(), ScandalError> {
156 let mut pending = vec![(proto, 1usize)];
157 let mut nodes = 0;
158 let mut metadata_bytes = 0usize;
159 while let Some((node, depth)) = pending.pop() {
160 nodes += 1;
161 validate_shape(nodes, pending.len(), node.children.len(), depth)?;
162 metadata_bytes = metadata_bytes
163 .saturating_add(node.id.len())
164 .saturating_add(node.metadata.as_ref().map_or(0, Vec::len));
165 if metadata_bytes > MAX_METADATA_BYTES {
166 return Err(invalid("expression metadata exceeds 256 KiB"));
167 }
168 pending.extend(node.children.iter().map(|child| (child, depth + 1)));
169 }
170 if proto.encoded_len() > MAX_ENCODED_BYTES {
171 return Err(invalid("expression protobuf exceeds 512 KiB"));
172 }
173 Ok(())
174}
175
176fn validate_shape(
177 nodes: usize,
178 pending: usize,
179 children: usize,
180 depth: usize,
181) -> Result<(), ScandalError> {
182 if nodes.saturating_add(pending).saturating_add(children) > MAX_NODES || depth > MAX_DEPTH {
183 return Err(invalid("expression exceeds 4096 nodes or 64 levels"));
184 }
185 Ok(())
186}
187
188fn invalid(error: impl Display) -> ScandalError {
189 ScandalError::InvalidArgument {
190 message: error.to_string(),
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use vortex_array::expr::{and, col, eq, gt, lit, not};
197 use vortex_array::proto::expr::Expr;
198 use vortex_session::VortexSession;
199
200 use super::{Expression, case_when, spql_call};
201
202 #[test]
203 fn case_when_round_trips_with_the_default_vortex_session() {
204 let branches = vec![
205 (eq(col("a"), lit(1i64)), lit("one")),
206 (eq(col("a"), lit(2i64)), lit("two")),
207 ];
208 for otherwise in [None, Some(lit("other"))] {
209 let original = case_when(branches.clone(), otherwise).unwrap();
210 let expression = Expression::new(original.clone()).unwrap();
211 assert_eq!(
212 expression.to_vortex(&VortexSession::empty()).unwrap(),
213 original
214 );
215 }
216 assert!(case_when(Vec::new(), Some(lit(1i64))).is_err());
217 }
218
219 #[test]
220 fn a_conjunction_decodes_to_both_terms() {
221 let left = eq(col("episode"), lit(7u64));
222 let right = gt(col("log_time"), lit(100i64));
223 let conjunction = Expression::new(left.clone())
224 .unwrap()
225 .and(&Expression::new(right.clone()).unwrap())
226 .unwrap();
227 assert_eq!(
228 conjunction.to_vortex(&VortexSession::empty()).unwrap(),
229 and(left, right)
230 );
231 }
232
233 #[test]
234 fn vortex_expression_round_trips_and_rejects_unbounded_or_invalid_wire() {
235 let original = eq(col("value"), lit(u64::MAX));
236 let expression = Expression::new(original.clone()).unwrap();
237 let decoded: Expression =
238 serde_json::from_value(serde_json::to_value(&expression).unwrap()).unwrap();
239 assert_eq!(
240 decoded.to_vortex(&VortexSession::empty()).unwrap(),
241 original
242 );
243 assert_eq!(
244 Expression::from_bytes(&expression.to_bytes().unwrap()).unwrap(),
245 expression
246 );
247 assert!(Expression::from_bytes(&[0xff]).is_err());
248 let unknown = Expression::from_proto(Expr {
249 id: "unregistered.function".into(),
250 ..Default::default()
251 })
252 .unwrap();
253 assert!(unknown.to_vortex(&VortexSession::empty()).is_err());
254 assert!(
255 Expression("not base64".into())
256 .to_vortex(&VortexSession::empty())
257 .is_err()
258 );
259 let mut deep = lit(true);
260 for _ in 0..64 {
261 deep = not(deep);
262 }
263 assert!(Expression::new(deep).is_err());
264 let wide = Expr {
265 id: "vortex.root".into(),
266 children: vec![Expr::default(); 4096],
267 ..Default::default()
268 };
269 assert!(Expression::from_proto(wide).is_err());
270 assert!(Expression::new(lit("x".repeat(256 * 1024))).is_err());
271 }
272
273 #[test]
274 fn composed_spql_call_serializes_like_a_direct_call() {
275 let composed = spql_call("spql.str.upper", vec![col("label")]).unwrap();
276 let direct = Expression::call(
277 "spql.str.upper",
278 vec![Expression::new(col("label")).unwrap()],
279 )
280 .unwrap();
281 assert_eq!(Expression::new(composed.clone()).unwrap(), direct);
282 let nested = Expression::new(not(eq(composed, lit("X")))).unwrap();
283 assert_eq!(nested.to_bytes().unwrap().len(), nested.encoded_len());
284 }
285}