@@ -64,6 +64,67 @@ use datafusion_physical_expr_common::physical_expr::{PhysicalExprRef, fmt_sql};
6464use datafusion_physical_expr_common:: sort_expr:: { LexOrdering , OrderingRequirements } ;
6565use futures:: StreamExt ;
6666
67+
68+ /// Null-aware (`NOT IN`) semantics of a soft merge join, derived from
69+ /// [`SortMergeJoinExec::null_aware`] and the join type.
70+ ///
71+ /// Only these combinations are legal (see [`Self::try_new`]), so the
72+ /// stream matches on this instead of re-checking `null_aware && join_type == ..`.
73+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
74+ pub ( super ) enum NullAwareMode {
75+ /// `left.key NOT IN (right.key)`: emits left rows.
76+ LeftAnti
77+ }
78+
79+ impl NullAwareMode {
80+ /// Validates that `null_aware` may be set for this join and returns its mode.
81+ pub ( super ) fn try_new (
82+ join_type : JoinType ,
83+ num_keys : usize ,
84+ has_filter : bool ,
85+ null_equality : NullEquality ,
86+ sort_options : & [ SortOptions ] ,
87+ ) -> Result < Self > {
88+ if num_keys != 1 || has_filter {
89+ return plan_err ! (
90+ "null-aware SortMergeJoin requires one key and no filter"
91+ ) ;
92+ }
93+
94+ // NULL keys must not match each other.
95+ // The null-aware execution path handles UNKNOWN results separately.
96+ // UNKNOWN happens when `WHERE x NOT IN (NULL)`
97+ if null_equality != NullEquality :: NullEqualsNothing {
98+ return plan_err ! (
99+ "null-aware SortMergeJoin requires NullEqualsNothing"
100+ ) ;
101+ }
102+
103+ let mode = match join_type {
104+ JoinType :: LeftAnti => Self :: LeftAnti ,
105+ _ => {
106+ return plan_err ! (
107+ "null-aware SortMergeJoin only supports LeftAnti, got {join_type}"
108+ ) ;
109+ }
110+ } ;
111+
112+ match mode {
113+ Self :: LeftAnti => {
114+ // With a single right-side partition and NULLS FIRST,
115+ // the first row reveals whether the subquery contains NULL.
116+ if sort_options. len ( ) != 1 || !sort_options[ 0 ] . nulls_first {
117+ return plan_err ! (
118+ "null-aware {join_type} SortMergeJoin requires NULLS FIRST ordering"
119+ ) ;
120+ }
121+ Ok ( mode)
122+ }
123+ }
124+ }
125+ }
126+
127+
67128/// Join execution plan that executes equi-join predicates on multiple partitions using Sort-Merge
68129/// join algorithm and applies an optional filter post join. Can be used to join arbitrarily large
69130/// inputs where one or both of the inputs don't fit in the available memory.
0 commit comments