vortex_array/stats/
rewrite.rs1use std::fmt::Debug;
7use std::sync::Arc;
8
9use vortex_error::VortexResult;
10use vortex_error::vortex_ensure;
11use vortex_session::VortexSession;
12use vortex_utils::iter::ReduceBalancedIterExt;
13
14use crate::dtype::DType;
15use crate::expr::BoundExpression;
16use crate::scalar_fn::ScalarFnId;
17use crate::scalar_fn::ScalarFnVTableExt;
18use crate::scalar_fn::fns::binary::Binary;
19use crate::scalar_fn::fns::operators::Operator;
20use crate::stats::session::StatsSessionExt;
21
22mod builtins;
23
24pub(crate) use builtins::register_builtins;
25
26pub type StatsRewriteRuleRef = Arc<dyn StatsRewriteRule>;
28
29pub trait StatsRewriteRule: Debug + Send + Sync + 'static {
47 fn scalar_fn_id(&self) -> ScalarFnId;
49
50 fn falsify(
58 &self,
59 expr: &BoundExpression,
60 ctx: &StatsRewriteCtx<'_>,
61 ) -> VortexResult<Option<BoundExpression>> {
62 _ = expr;
63 _ = ctx;
64 Ok(None)
65 }
66
67 fn satisfy(
78 &self,
79 expr: &BoundExpression,
80 ctx: &StatsRewriteCtx<'_>,
81 ) -> VortexResult<Option<BoundExpression>> {
82 _ = expr;
83 _ = ctx;
84 Ok(None)
85 }
86}
87
88pub struct StatsRewriteCtx<'a> {
90 session: &'a VortexSession,
91}
92
93impl<'a> StatsRewriteCtx<'a> {
94 pub fn new(session: &'a VortexSession) -> Self {
96 Self { session }
97 }
98
99 pub fn session(&self) -> &'a VortexSession {
101 self.session
102 }
103
104 pub fn return_dtype(&self, expr: &BoundExpression) -> VortexResult<DType> {
106 Ok(expr.dtype().clone())
107 }
108
109 pub fn falsify(&self, expr: &BoundExpression) -> VortexResult<Option<BoundExpression>> {
111 self.ensure_predicate(expr)?;
112 rewrite(expr, self, StatsRewriteRule::falsify)
113 }
114
115 pub fn satisfy(&self, expr: &BoundExpression) -> VortexResult<Option<BoundExpression>> {
117 self.ensure_predicate(expr)?;
118 rewrite(expr, self, StatsRewriteRule::satisfy)
119 }
120
121 fn ensure_predicate(&self, expr: &BoundExpression) -> VortexResult<()> {
122 let dtype = self.return_dtype(expr)?;
123 vortex_ensure!(
124 matches!(dtype, DType::Bool(_)),
125 "Stats rewrites require a boolean predicate, got {dtype}",
126 );
127 Ok(())
128 }
129}
130
131fn rewrite(
132 expr: &BoundExpression,
133 ctx: &StatsRewriteCtx<'_>,
134 apply: fn(
135 &dyn StatsRewriteRule,
136 &BoundExpression,
137 &StatsRewriteCtx<'_>,
138 ) -> VortexResult<Option<BoundExpression>>,
139) -> VortexResult<Option<BoundExpression>> {
140 let Some(scalar_fn) = expr.as_scalar() else {
141 return Ok(None);
142 };
143 let rules = ctx.session().stats().rewrite_rules_for(scalar_fn.id());
144 let Some(rules) = rules else {
145 return Ok(None);
146 };
147
148 let mut rewrites = Vec::new();
149 for rule in rules.iter() {
150 if let Some(rewrite) = apply(rule.as_ref(), expr, ctx)? {
151 rewrites.push(rewrite);
152 }
153 }
154
155 rewrites
156 .into_iter()
157 .try_reduce_balanced(|lhs, rhs| Binary.try_new_bound_expr(Operator::Or, [lhs, rhs]))
158}
159
160#[cfg(test)]
161mod tests {
162 use vortex_error::VortexResult;
163
164 use super::StatsRewriteCtx;
165 use super::StatsRewriteRule;
166 use crate::dtype::DType;
167 use crate::dtype::Nullability;
168 use crate::dtype::PType;
169 use crate::expr::BoundExpression;
170 use crate::expr::lit;
171 use crate::expr::or;
172 use crate::scalar_fn::ScalarFnId;
173 use crate::scalar_fn::ScalarFnVTable;
174 use crate::scalar_fn::fns::literal::Literal;
175 use crate::stats::session::StatsSessionExt;
176
177 #[derive(Debug)]
178 struct StaticLiteralRule {
179 falsifier: Option<BoundExpression>,
180 satisfier: Option<BoundExpression>,
181 }
182
183 impl StatsRewriteRule for StaticLiteralRule {
184 fn scalar_fn_id(&self) -> ScalarFnId {
185 Literal.id()
186 }
187
188 fn falsify(
189 &self,
190 _expr: &BoundExpression,
191 _ctx: &StatsRewriteCtx<'_>,
192 ) -> VortexResult<Option<BoundExpression>> {
193 Ok(self.falsifier.clone())
194 }
195
196 fn satisfy(
197 &self,
198 _expr: &BoundExpression,
199 _ctx: &StatsRewriteCtx<'_>,
200 ) -> VortexResult<Option<BoundExpression>> {
201 Ok(self.satisfier.clone())
202 }
203 }
204
205 #[test]
206 fn combines_multiple_falsifiers_with_or() -> VortexResult<()> {
207 let session = crate::array_session();
208 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
209 session.stats().register_rewrite(StaticLiteralRule {
210 falsifier: Some(lit(false).bind(&dtype)?),
211 satisfier: None,
212 });
213 session.stats().register_rewrite(StaticLiteralRule {
214 falsifier: Some(lit(true).bind(&dtype)?),
215 satisfier: None,
216 });
217
218 assert_eq!(
219 lit(true).bind(&dtype)?.falsify(&session)?,
220 Some(or(lit(false), lit(true)).bind(&dtype)?)
221 );
222 Ok(())
223 }
224
225 #[test]
226 fn combines_multiple_satisfiers_with_or() -> VortexResult<()> {
227 let session = crate::array_session();
228 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
229 session.stats().register_rewrite(StaticLiteralRule {
230 falsifier: None,
231 satisfier: Some(lit(false).bind(&dtype)?),
232 });
233 session.stats().register_rewrite(StaticLiteralRule {
234 falsifier: None,
235 satisfier: Some(lit(true).bind(&dtype)?),
236 });
237
238 assert_eq!(
239 lit(true).bind(&dtype)?.satisfy(&session)?,
240 Some(or(lit(false), lit(true)).bind(&dtype)?)
241 );
242 Ok(())
243 }
244
245 #[test]
246 fn unregistered_expression_has_no_rewrite() -> VortexResult<()> {
247 let session = crate::array_session();
248 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
249
250 let expr = lit(true).bind(&dtype)?;
251 assert_eq!(expr.falsify(&session)?, None);
252 assert_eq!(expr.satisfy(&session)?, None);
253 Ok(())
254 }
255
256 #[test]
257 fn non_predicate_expression_errors() -> VortexResult<()> {
258 let session = crate::array_session();
259 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
260
261 let expr = lit(7).bind(&dtype)?;
262 assert!(expr.falsify(&session).is_err());
263 assert!(expr.satisfy(&session).is_err());
264 Ok(())
265 }
266}