Skip to content

Commit 360aa58

Browse files
committed
Make stats binders immutable
Signed-off-by: "Nicholas Gates" <nick@nickgates.com>
1 parent 32241d2 commit 360aa58

4 files changed

Lines changed: 49 additions & 50 deletions

File tree

vortex-array/src/expr/pruning/pruning_expr.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4+
use std::cell::RefCell;
45
use std::iter;
56

67
use itertools::Itertools;
@@ -70,14 +71,14 @@ pub fn checked_pruning_expr(
7071
return Ok(None);
7172
};
7273

73-
let mut binder = RequiredStatsBinder {
74+
let binder = RequiredStatsBinder {
7475
scope,
7576
available_stats,
76-
required_stats: Relation::new(),
77-
bound_stats: Vec::new(),
77+
required_stats: RefCell::new(Relation::new()),
78+
bound_stats: RefCell::new(Vec::new()),
7879
};
79-
let lowered = bind_stats(predicate, &mut binder)?;
80-
let required_stats = filter_required_stats(&lowered, binder.required_stats);
80+
let lowered = bind_stats(predicate, &binder)?;
81+
let required_stats = filter_required_stats(&lowered, binder.required_stats.into_inner());
8182
// If no stats-table fields remain, only a constant `true` proof can prune.
8283
// `false`, `null`, and non-constant expressions cannot justify building a
8384
// stats-table pruning expression.
@@ -91,8 +92,8 @@ pub fn checked_pruning_expr(
9192
struct RequiredStatsBinder<'a> {
9293
scope: &'a DType,
9394
available_stats: &'a FieldPathSet,
94-
required_stats: RequiredStats,
95-
bound_stats: Vec<(FieldName, DType)>,
95+
required_stats: RefCell<RequiredStats>,
96+
bound_stats: RefCell<Vec<(FieldName, DType)>>,
9697
}
9798

9899
impl StatBinder for RequiredStatsBinder<'_> {
@@ -102,13 +103,13 @@ impl StatBinder for RequiredStatsBinder<'_> {
102103

103104
fn bound_scope(&self) -> DType {
104105
DType::Struct(
105-
StructFields::from_iter(self.bound_stats.iter().cloned()),
106+
StructFields::from_iter(self.bound_stats.borrow().iter().cloned()),
106107
Nullability::NonNullable,
107108
)
108109
}
109110

110111
fn bind_stat(
111-
&mut self,
112+
&self,
112113
input: &Expression,
113114
stat: Stat,
114115
stat_dtype: &DType,
@@ -129,21 +130,20 @@ impl StatBinder for RequiredStatsBinder<'_> {
129130
}
130131

131132
let stat_field_name = field_path_stat_field_name(&field_path, stat);
132-
if self
133-
.bound_stats
133+
let mut bound_stats = self.bound_stats.borrow_mut();
134+
if bound_stats
134135
.iter()
135136
.all(|(field_name, _)| field_name != stat_field_name)
136137
{
137-
self.bound_stats
138-
.push((stat_field_name.clone(), stat_dtype.clone()));
138+
bound_stats.push((stat_field_name.clone(), stat_dtype.clone()));
139139
}
140140

141-
self.required_stats.insert(field_path, stat);
141+
self.required_stats.borrow_mut().insert(field_path, stat);
142142
Ok(Some(get_item(stat_field_name, root())))
143143
}
144144

145145
fn bind_aggregate(
146-
&mut self,
146+
&self,
147147
input: &Expression,
148148
aggregate_fn: &AggregateFnRef,
149149
stat_dtype: &DType,

vortex-array/src/stats/bind.rs

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub trait StatBinder {
4747
/// then call [`Self::missing_stat`] with the dtype expected from the
4848
/// original `vortex.stat` expression.
4949
fn bind_stat(
50-
&mut self,
50+
&self,
5151
input: &Expression,
5252
stat: Stat,
5353
stat_dtype: &DType,
@@ -59,7 +59,7 @@ pub trait StatBinder {
5959
/// [`Stat`] slots. Binders that store richer aggregate stats can override
6060
/// this method without extending the generic stats binding walker.
6161
fn bind_aggregate(
62-
&mut self,
62+
&self,
6363
input: &Expression,
6464
aggregate_fn: &AggregateFnRef,
6565
stat_dtype: &DType,
@@ -68,11 +68,7 @@ pub trait StatBinder {
6868
}
6969

7070
/// Bind one of the legacy stat slots for `input`.
71-
fn bind_legacy_stat(
72-
&mut self,
73-
input: &Expression,
74-
stat: Stat,
75-
) -> VortexResult<Option<Expression>> {
71+
fn bind_legacy_stat(&self, input: &Expression, stat: Stat) -> VortexResult<Option<Expression>> {
7672
let input_dtype = input.return_dtype(self.scope())?;
7773
let Some(stat_dtype) = stat.dtype(&input_dtype) else {
7874
return Ok(None);
@@ -84,7 +80,7 @@ pub trait StatBinder {
8480
///
8581
/// The default is a nullable null literal, which preserves three-valued
8682
/// pruning semantics for stats-table execution.
87-
fn missing_stat(&mut self, dtype: DType) -> VortexResult<Expression> {
83+
fn missing_stat(&self, dtype: DType) -> VortexResult<Expression> {
8884
Ok(null_expr(dtype))
8985
}
9086
}
@@ -94,7 +90,10 @@ pub trait StatBinder {
9490
/// The predicate is usually the output of a stats rewrite rule. Rewrite rules
9591
/// are responsible for expressing stat semantics; binding maps aggregate-backed
9692
/// stat requests to the concrete stats representation supported by the binder.
97-
pub fn bind_stats(predicate: Expression, binder: &mut impl StatBinder) -> VortexResult<Expression> {
93+
pub fn bind_stats<B: StatBinder + ?Sized>(
94+
predicate: Expression,
95+
binder: &B,
96+
) -> VortexResult<Expression> {
9897
let scope = binder.scope().clone();
9998
let lowered = predicate
10099
.transform_down(|expr| {
@@ -120,7 +119,7 @@ pub fn bind_stats(predicate: Expression, binder: &mut impl StatBinder) -> Vortex
120119
/// This is an opt-in helper for stats backends that materialize `NaNCount` and
121120
/// `NullCount`, but do not materialize aggregate boolean stats directly.
122121
pub fn bind_legacy_count_aggregate<B: StatBinder + ?Sized>(
123-
binder: &mut B,
122+
binder: &B,
124123
input: &Expression,
125124
aggregate_fn: &AggregateFnRef,
126125
) -> VortexResult<Option<Expression>> {
@@ -157,7 +156,7 @@ pub fn bind_legacy_count_aggregate<B: StatBinder + ?Sized>(
157156

158157
/// Bind an aggregate function that has a direct legacy [`Stat`] slot.
159158
pub fn bind_direct_aggregate_stat<B: StatBinder + ?Sized>(
160-
binder: &mut B,
159+
binder: &B,
161160
input: &Expression,
162161
aggregate_fn: &AggregateFnRef,
163162
stat_dtype: &DType,
@@ -174,7 +173,7 @@ pub fn bind_direct_aggregate_stat<B: StatBinder + ?Sized>(
174173
/// `NaNCount` and `NullCount`, then fall back to direct aggregate-to-stat
175174
/// mappings.
176175
pub fn bind_legacy_count_or_direct_aggregate<B: StatBinder + ?Sized>(
177-
binder: &mut B,
176+
binder: &B,
178177
input: &Expression,
179178
aggregate_fn: &AggregateFnRef,
180179
stat_dtype: &DType,
@@ -189,7 +188,7 @@ pub fn bind_legacy_count_or_direct_aggregate<B: StatBinder + ?Sized>(
189188
fn bind_stat_fn(
190189
expr: &Expression,
191190
scope: &DType,
192-
binder: &mut impl StatBinder,
191+
binder: &(impl StatBinder + ?Sized),
193192
) -> VortexResult<Option<Expression>> {
194193
let options = expr.as_::<StatFn>();
195194
let aggregate_fn = options.aggregate_fn();
@@ -258,7 +257,7 @@ mod tests {
258257
}
259258

260259
fn bind_stat(
261-
&mut self,
260+
&self,
262261
_input: &Expression,
263262
stat: Stat,
264263
_stat_dtype: &DType,
@@ -271,7 +270,7 @@ mod tests {
271270
}
272271

273272
fn bind_aggregate(
274-
&mut self,
273+
&self,
275274
input: &Expression,
276275
aggregate_fn: &AggregateFnRef,
277276
stat_dtype: &DType,
@@ -282,33 +281,33 @@ mod tests {
282281

283282
#[test]
284283
fn all_non_nan_binds_to_nan_count_zero() -> VortexResult<()> {
285-
let mut binder = TestBinder::new(true);
284+
let binder = TestBinder::new(true);
286285

287-
let bound = bind_stats(all_non_nan(col("f")), &mut binder)?;
286+
let bound = bind_stats(all_non_nan(col("f")), &binder)?;
288287

289288
assert_eq!(bound, eq(col("f_nan_count"), lit(0u64)));
290289
Ok(())
291290
}
292291

293292
#[test]
294293
fn all_non_nan_lowers_to_null_when_nan_count_is_missing() -> VortexResult<()> {
295-
let mut binder = TestBinder::new(false);
294+
let binder = TestBinder::new(false);
296295

297-
let bound = bind_stats(all_non_nan(col("f")), &mut binder)?;
296+
let bound = bind_stats(all_non_nan(col("f")), &binder)?;
298297

299298
assert_eq!(bound, lit(Scalar::null(DType::Bool(Nullability::Nullable))));
300299
Ok(())
301300
}
302301

303302
#[test]
304303
fn missing_stats_fold_when_kleene_semantics_allow_it() -> VortexResult<()> {
305-
let mut binder = TestBinder::new(false);
304+
let binder = TestBinder::new(false);
306305

307-
let bound = bind_stats(and(lit(false), all_non_nan(col("f"))), &mut binder)?;
306+
let bound = bind_stats(and(lit(false), all_non_nan(col("f"))), &binder)?;
308307

309308
assert_eq!(bound, lit(false));
310309

311-
let bound = bind_stats(or(lit(true), all_non_nan(col("f"))), &mut binder)?;
310+
let bound = bind_stats(or(lit(true), all_non_nan(col("f"))), &binder)?;
312311

313312
assert_eq!(bound, lit(true));
314313
Ok(())
@@ -328,7 +327,7 @@ mod tests {
328327
}
329328

330329
fn bind_stat(
331-
&mut self,
330+
&self,
332331
input: &Expression,
333332
stat: Stat,
334333
stat_dtype: &DType,
@@ -337,19 +336,19 @@ mod tests {
337336
}
338337
}
339338

340-
let mut binder = DefaultBinder(TestBinder::new(true));
339+
let binder = DefaultBinder(TestBinder::new(true));
341340

342-
let bound = bind_stats(all_non_nan(col("f")), &mut binder)?;
341+
let bound = bind_stats(all_non_nan(col("f")), &binder)?;
343342

344343
assert_eq!(bound, lit(Scalar::null(DType::Bool(Nullability::Nullable))));
345344
Ok(())
346345
}
347346

348347
#[test]
349348
fn unrelated_expressions_do_not_request_nan_count() -> VortexResult<()> {
350-
let mut binder = TestBinder::new(false);
349+
let binder = TestBinder::new(false);
351350

352-
let bound = bind_stats(is_null(col("f")), &mut binder)?;
351+
let bound = bind_stats(is_null(col("f")), &binder)?;
353352

354353
assert_eq!(bound, is_null(col("f")));
355354
Ok(())

vortex-file/src/v2/file_stats_reader.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ impl FileStatsLayoutReader {
121121
}
122122

123123
fn lower_stats(&self, predicate: Expression) -> VortexResult<Expression> {
124-
let mut binder = FileStatsBinder { reader: self };
125-
bind_stats(predicate, &mut binder)
124+
let binder = FileStatsBinder { reader: self };
125+
bind_stats(predicate, &binder)
126126
}
127127

128128
pub fn file_stats(&self) -> &FileStatistics {
@@ -144,7 +144,7 @@ impl StatBinder for FileStatsBinder<'_> {
144144
}
145145

146146
fn bind_stat(
147-
&mut self,
147+
&self,
148148
input: &Expression,
149149
stat: Stat,
150150
_stat_dtype: &DType,
@@ -156,7 +156,7 @@ impl StatBinder for FileStatsBinder<'_> {
156156
}
157157

158158
fn bind_aggregate(
159-
&mut self,
159+
&self,
160160
input: &Expression,
161161
aggregate_fn: &AggregateFnRef,
162162
stat_dtype: &DType,

vortex-layout/src/layouts/zoned/zone_map.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ impl ZoneMap {
122122
}
123123

124124
fn lower_stats(&self, predicate: Expression) -> VortexResult<Expression> {
125-
let mut binder = ZoneMapStatsBinder { zone_map: self };
126-
bind_stats(predicate, &mut binder)
125+
let binder = ZoneMapStatsBinder { zone_map: self };
126+
bind_stats(predicate, &binder)
127127
}
128128
}
129129

@@ -141,7 +141,7 @@ impl StatBinder for ZoneMapStatsBinder<'_> {
141141
}
142142

143143
fn bind_stat(
144-
&mut self,
144+
&self,
145145
input: &Expression,
146146
stat: Stat,
147147
_stat_dtype: &DType,
@@ -161,7 +161,7 @@ impl StatBinder for ZoneMapStatsBinder<'_> {
161161
}
162162

163163
fn bind_aggregate(
164-
&mut self,
164+
&self,
165165
input: &Expression,
166166
aggregate_fn: &AggregateFnRef,
167167
stat_dtype: &DType,

0 commit comments

Comments
 (0)