Skip to content

Commit c936b6a

Browse files
committed
Refactor is_restrict_null_predicate in utils.rs
Restore is_restrict_null_predicate to its original behavior by removing the broader syntactic null-restriction path and its associated test scaffolding. Maintain early rejection for the push_down_filter caller while eliminating extra tree-walk work on common paths. Confirmed changes by running tests and formatting.
1 parent 37bcc07 commit c936b6a

File tree

2 files changed

+5
-474
lines changed

2 files changed

+5
-474
lines changed

datafusion/optimizer/src/utils.rs

Lines changed: 5 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717

1818
//! Utility functions leveraged by the query optimizer rules
1919
20-
mod null_restriction;
21-
2220
use std::collections::{BTreeSet, HashMap, HashSet};
2321
use std::sync::Arc;
2422

@@ -38,12 +36,6 @@ use log::{debug, trace};
3836
/// as it was initially placed here and then moved elsewhere.
3937
pub use datafusion_expr::expr_rewriter::NamePreserver;
4038

41-
#[cfg(test)]
42-
use self::test_eval_mode::{
43-
NullRestrictionEvalMode, null_restriction_eval_mode,
44-
set_null_restriction_eval_mode_for_test, with_null_restriction_eval_mode_for_test,
45-
};
46-
4739
/// Returns true if `expr` contains all columns in `schema_cols`
4840
pub(crate) fn has_all_column_refs(expr: &Expr, schema_cols: &HashSet<Column>) -> bool {
4941
expr.column_refs()
@@ -86,40 +78,17 @@ pub fn is_restrict_null_predicate<'a>(
8678
// Collect join columns so they can be used in both the fast-path check and the
8779
// fallback evaluation path below.
8880
let join_cols: HashSet<&Column> = join_cols_of_predicate.into_iter().collect();
89-
let column_refs = predicate.column_refs();
90-
9181
// Fast path: if the predicate references columns outside the join key set,
9282
// `evaluate_expr_with_null_column` would fail because the null schema only
9383
// contains a placeholder for the join key columns. Callers treat such errors as
9484
// non-restricting (false) via `matches!(_, Ok(true))`, so we return false early
9585
// and avoid the expensive physical-expression compilation pipeline entirely.
96-
if !column_refs.iter().all(|column| join_cols.contains(*column)) {
97-
return Ok(false);
98-
}
99-
100-
#[cfg(test)]
101-
if matches!(
102-
null_restriction_eval_mode(),
103-
NullRestrictionEvalMode::AuthoritativeOnly
104-
) {
105-
return authoritative_restrict_null_predicate(predicate, join_cols);
106-
}
107-
108-
if let Some(is_restricting) =
109-
null_restriction::syntactic_restrict_null_predicate(&predicate, &join_cols)
86+
if !predicate
87+
.column_refs()
88+
.iter()
89+
.all(|column| join_cols.contains(*column))
11090
{
111-
#[cfg(debug_assertions)]
112-
{
113-
let authoritative = authoritative_restrict_null_predicate(
114-
predicate.clone(),
115-
join_cols.iter().copied(),
116-
)?;
117-
debug_assert_eq!(
118-
is_restricting, authoritative,
119-
"syntactic fast path disagrees with authoritative null-restriction evaluation for predicate: {predicate}"
120-
);
121-
}
122-
return Ok(is_restricting);
91+
return Ok(false);
12392
}
12493

12594
authoritative_restrict_null_predicate(predicate, join_cols)
@@ -198,75 +167,13 @@ fn coerce(expr: Expr, schema: &DFSchema) -> Result<Expr> {
198167
expr.rewrite(&mut expr_rewrite).data()
199168
}
200169

201-
#[cfg(test)]
202-
mod test_eval_mode {
203-
use std::cell::Cell;
204-
205-
/// Null restriction evaluation mode for optimizer tests.
206-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
207-
pub(crate) enum NullRestrictionEvalMode {
208-
Auto,
209-
AuthoritativeOnly,
210-
}
211-
212-
thread_local! {
213-
static NULL_RESTRICTION_EVAL_MODE: Cell<NullRestrictionEvalMode> =
214-
const { Cell::new(NullRestrictionEvalMode::Auto) };
215-
}
216-
217-
pub(crate) fn set_null_restriction_eval_mode_for_test(mode: NullRestrictionEvalMode) {
218-
NULL_RESTRICTION_EVAL_MODE.with(|eval_mode| eval_mode.set(mode));
219-
}
220-
221-
pub(crate) fn null_restriction_eval_mode() -> NullRestrictionEvalMode {
222-
NULL_RESTRICTION_EVAL_MODE.with(Cell::get)
223-
}
224-
225-
pub(crate) fn with_null_restriction_eval_mode_for_test<T>(
226-
mode: NullRestrictionEvalMode,
227-
f: impl FnOnce() -> T,
228-
) -> T {
229-
struct NullRestrictionEvalModeReset(NullRestrictionEvalMode);
230-
231-
impl Drop for NullRestrictionEvalModeReset {
232-
fn drop(&mut self) {
233-
set_null_restriction_eval_mode_for_test(self.0);
234-
}
235-
}
236-
237-
let previous_mode = null_restriction_eval_mode();
238-
set_null_restriction_eval_mode_for_test(mode);
239-
let _reset = NullRestrictionEvalModeReset(previous_mode);
240-
f()
241-
}
242-
}
243-
244170
#[cfg(test)]
245171
mod tests {
246172
use super::*;
247-
use std::panic::{AssertUnwindSafe, catch_unwind};
248-
249173
use datafusion_expr::{
250174
Operator, binary_expr, case, col, in_list, is_null, lit, when,
251175
};
252176

253-
fn restrict_null_predicate_in_modes(
254-
predicate: Expr,
255-
join_cols: &[Column],
256-
) -> Result<(bool, bool)> {
257-
let auto_result = with_null_restriction_eval_mode_for_test(
258-
NullRestrictionEvalMode::Auto,
259-
|| is_restrict_null_predicate(predicate.clone(), join_cols.iter()),
260-
)?;
261-
262-
let authoritative_result = with_null_restriction_eval_mode_for_test(
263-
NullRestrictionEvalMode::AuthoritativeOnly,
264-
|| is_restrict_null_predicate(predicate.clone(), join_cols.iter()),
265-
)?;
266-
267-
Ok((auto_result, authoritative_result))
268-
}
269-
270177
#[test]
271178
fn expr_is_restrict_null_predicate() -> Result<()> {
272179
let test_cases = vec![
@@ -409,118 +316,4 @@ mod tests {
409316

410317
Ok(())
411318
}
412-
413-
#[test]
414-
fn syntactic_fast_path_matches_authoritative_evaluator() -> Result<()> {
415-
let test_cases = vec![
416-
is_null(col("a")),
417-
Expr::IsNotNull(Box::new(col("a"))),
418-
binary_expr(col("a"), Operator::Gt, lit(8i64)),
419-
binary_expr(col("a"), Operator::Eq, lit(ScalarValue::Null)),
420-
binary_expr(col("a"), Operator::And, lit(true)),
421-
binary_expr(col("a"), Operator::Or, lit(false)),
422-
Expr::Not(Box::new(col("a").is_true())),
423-
col("a").is_true(),
424-
col("a").is_false(),
425-
col("a").is_unknown(),
426-
col("a").is_not_true(),
427-
col("a").is_not_false(),
428-
col("a").is_not_unknown(),
429-
col("a").between(lit(1i64), lit(10i64)),
430-
binary_expr(
431-
when(Expr::IsNotNull(Box::new(col("a"))), col("a"))
432-
.otherwise(col("b"))?,
433-
Operator::Gt,
434-
lit(2i64),
435-
),
436-
case(col("a"))
437-
.when(lit(1i64), lit(true))
438-
.otherwise(lit(false))?,
439-
case(col("a"))
440-
.when(lit(0i64), lit(false))
441-
.otherwise(lit(true))?,
442-
binary_expr(
443-
case(col("a"))
444-
.when(lit(0i64), lit(true))
445-
.otherwise(lit(false))?,
446-
Operator::Or,
447-
lit(false),
448-
),
449-
binary_expr(
450-
case(lit(1i64))
451-
.when(lit(1i64), lit(ScalarValue::Null))
452-
.otherwise(lit(false))?,
453-
Operator::IsNotDistinctFrom,
454-
lit(true),
455-
),
456-
];
457-
458-
for predicate in test_cases {
459-
let join_cols = predicate.column_refs();
460-
if let Some(syntactic) = null_restriction::syntactic_restrict_null_predicate(
461-
&predicate, &join_cols,
462-
) {
463-
let authoritative = authoritative_restrict_null_predicate(
464-
predicate.clone(),
465-
join_cols.iter().copied(),
466-
)
467-
.unwrap_or_else(|error| {
468-
panic!(
469-
"authoritative evaluator failed for predicate `{predicate}`: {error}"
470-
)
471-
});
472-
assert_eq!(
473-
syntactic, authoritative,
474-
"syntactic fast path disagrees with authoritative evaluator for predicate: {predicate}",
475-
);
476-
}
477-
}
478-
479-
Ok(())
480-
}
481-
482-
#[test]
483-
fn null_restriction_eval_mode_auto_vs_authoritative_only() -> Result<()> {
484-
let predicate = binary_expr(col("a"), Operator::Gt, lit(8i64));
485-
let join_cols_of_predicate = predicate
486-
.column_refs()
487-
.into_iter()
488-
.cloned()
489-
.collect::<Vec<_>>();
490-
let (auto_result, authoritative_result) =
491-
restrict_null_predicate_in_modes(predicate, &join_cols_of_predicate)?;
492-
493-
assert_eq!(auto_result, authoritative_result);
494-
495-
Ok(())
496-
}
497-
498-
#[test]
499-
fn mixed_reference_predicate_remains_fast_pathed_in_authoritative_mode() -> Result<()>
500-
{
501-
let predicate = binary_expr(col("a"), Operator::Gt, col("b"));
502-
let join_cols = vec![Column::from_name("a")];
503-
let (auto_result, authoritative_only_result) =
504-
restrict_null_predicate_in_modes(predicate.clone(), &join_cols)?;
505-
506-
assert!(!auto_result, "{predicate}");
507-
assert!(!authoritative_only_result, "{predicate}");
508-
509-
Ok(())
510-
}
511-
512-
#[test]
513-
fn null_restriction_eval_mode_guard_restores_on_panic() {
514-
set_null_restriction_eval_mode_for_test(NullRestrictionEvalMode::Auto);
515-
516-
let result = catch_unwind(AssertUnwindSafe(|| {
517-
with_null_restriction_eval_mode_for_test(
518-
NullRestrictionEvalMode::AuthoritativeOnly,
519-
|| panic!("intentional panic to verify test mode reset"),
520-
)
521-
}));
522-
523-
assert!(result.is_err());
524-
assert_eq!(null_restriction_eval_mode(), NullRestrictionEvalMode::Auto);
525-
}
526319
}

0 commit comments

Comments
 (0)