@@ -144,6 +144,13 @@ pub struct FunctionalDependence {
144144 /// such as after LEFT JOIN or RIGHT JOIN operations, this property may
145145 /// change.
146146 pub nullable : bool ,
147+ /// The NULL-comparison semantics under which this dependency holds. The
148+ /// conservative default, [`NullEquality::NullEqualsNothing`], means it
149+ /// holds only across rows whose determinant contains no NULLs; e.g. a
150+ /// nullable `UNIQUE` constraint permits multiple NULL rows that may differ.
151+ /// [`NullEquality::NullEqualsNull`] means it also holds when NULL
152+ /// determinant values are treated as equal; e.g. a `GROUP BY` key.
153+ pub null_equality : NullEquality ,
147154 // The functional dependency mode:
148155 pub mode : Dependency ,
149156}
@@ -168,6 +175,8 @@ impl FunctionalDependence {
168175 source_indices,
169176 target_indices,
170177 nullable,
178+ // Assume the dependency does not hold across NULL rows by default:
179+ null_equality : NullEquality :: NullEqualsNothing ,
171180 // Start with the least restrictive mode by default:
172181 mode : Dependency :: Multi ,
173182 }
@@ -177,46 +186,43 @@ impl FunctionalDependence {
177186 self . mode = mode;
178187 self
179188 }
189+
190+ pub fn with_null_equality ( mut self , null_equality : NullEquality ) -> Self {
191+ self . null_equality = null_equality;
192+ self
193+ }
194+
195+ /// Returns `true` if this dependency remains usable for operations that
196+ /// treat NULL determinant values as equal (`GROUP BY`, `DISTINCT` and
197+ /// sorting, which place all NULL keys together): it must hold under
198+ /// NULLs-are-equal semantics, have a non-nullable determinant (e.g. a
199+ /// `PRIMARY KEY`), or have no nullable source field in the given `schema`.
200+ pub fn is_valid_across_nulls ( & self , schema : & DFSchema ) -> bool {
201+ self . null_equality == NullEquality :: NullEqualsNull
202+ || !self . nullable
203+ || self
204+ . source_indices
205+ . iter ( )
206+ . all ( |& source_idx| !schema. field ( source_idx) . is_nullable ( ) )
207+ }
180208}
181209
182210/// This object encapsulates all functional dependencies in a given relation.
183211#[ derive( Debug , Clone , PartialEq , Eq ) ]
184212pub struct FunctionalDependencies {
185213 deps : Vec < FunctionalDependence > ,
186- null_equalities : Vec < NullEquality > ,
187214}
188215
189216impl FunctionalDependencies {
190217 /// Creates an empty `FunctionalDependencies` object.
191218 pub fn empty ( ) -> Self {
192- Self {
193- deps : vec ! [ ] ,
194- null_equalities : vec ! [ ] ,
195- }
219+ Self { deps : vec ! [ ] }
196220 }
197221
198222 /// Creates a new `FunctionalDependencies` object from a vector of
199223 /// `FunctionalDependence` objects.
200224 pub fn new ( dependencies : Vec < FunctionalDependence > ) -> Self {
201- let null_equalities = vec ! [ NullEquality :: NullEqualsNothing ; dependencies. len( ) ] ;
202- Self :: new_with_null_equalities ( dependencies, null_equalities)
203- }
204-
205- fn new_with_null_equalities (
206- dependencies : Vec < FunctionalDependence > ,
207- null_equalities : Vec < NullEquality > ,
208- ) -> Self {
209- debug_assert_eq ! ( dependencies. len( ) , null_equalities. len( ) ) ;
210- Self {
211- deps : dependencies,
212- null_equalities,
213- }
214- }
215-
216- fn iter_with_null_equality (
217- & self ,
218- ) -> impl Iterator < Item = ( & FunctionalDependence , NullEquality ) > {
219- self . deps . iter ( ) . zip ( self . null_equalities . iter ( ) . copied ( ) )
225+ Self { deps : dependencies }
220226 }
221227
222228 /// Creates a new `FunctionalDependencies` object from the given constraints.
@@ -263,7 +269,6 @@ impl FunctionalDependencies {
263269 /// Merges the given functional dependencies with these.
264270 pub fn extend ( & mut self , other : FunctionalDependencies ) {
265271 self . deps . extend ( other. deps ) ;
266- self . null_equalities . extend ( other. null_equalities ) ;
267272 }
268273
269274 /// Sanity checks if functional dependencies are valid. For example, if
@@ -320,16 +325,13 @@ impl FunctionalDependencies {
320325 n_out : usize ,
321326 ) -> FunctionalDependencies {
322327 let mut projected_func_dependencies = vec ! [ ] ;
323- let mut projected_null_equalities = vec ! [ ] ;
324- for (
325- FunctionalDependence {
326- source_indices,
327- target_indices,
328- nullable,
329- mode,
330- } ,
328+ for FunctionalDependence {
329+ source_indices,
330+ target_indices,
331+ nullable,
331332 null_equality,
332- ) in self . iter_with_null_equality ( )
333+ mode,
334+ } in & self . deps
333335 {
334336 let new_source_indices =
335337 update_elements_with_matching_indices ( source_indices, proj_indices) ;
@@ -348,15 +350,12 @@ impl FunctionalDependencies {
348350 new_target_indices,
349351 * nullable,
350352 )
351- . with_mode ( * mode) ;
353+ . with_mode ( * mode)
354+ . with_null_equality ( * null_equality) ;
352355 projected_func_dependencies. push ( new_func_dependence) ;
353- projected_null_equalities. push ( null_equality) ;
354356 }
355357 }
356- FunctionalDependencies :: new_with_null_equalities (
357- projected_func_dependencies,
358- projected_null_equalities,
359- )
358+ FunctionalDependencies :: new ( projected_func_dependencies)
360359 }
361360
362361 /// This function joins this set of functional dependencies with the `other`
@@ -415,24 +414,13 @@ impl FunctionalDependencies {
415414 /// - If the dependency in question is PRIMARY KEY (i.e. not nullable), a new
416415 /// null value turns it into UNIQUE mode.
417416 fn downgrade_dependencies ( & mut self ) {
418- let dependencies = std:: mem:: take ( & mut self . deps ) ;
419- let null_equalities = std:: mem:: take ( & mut self . null_equalities ) ;
420- let mut retained_dependencies = Vec :: with_capacity ( dependencies. len ( ) ) ;
421- let mut retained_null_equalities = Vec :: with_capacity ( null_equalities. len ( ) ) ;
422-
423- for ( mut dependency, _) in dependencies. into_iter ( ) . zip ( null_equalities) {
424- // A dependency whose determinant was already nullable becomes
425- // invalid. A non-nullable determinant becomes nullable, and NULL
426- // values introduced by the join are not equal to one another.
427- if !dependency. nullable {
428- dependency. nullable = true ;
429- retained_dependencies. push ( dependency) ;
430- retained_null_equalities. push ( NullEquality :: NullEqualsNothing ) ;
431- }
432- }
433-
434- self . deps = retained_dependencies;
435- self . null_equalities = retained_null_equalities;
417+ // Delete nullable dependencies, since they are no longer valid:
418+ self . deps . retain ( |item| !item. nullable ) ;
419+ // Survivors become nullable, and the new NULLs are not equal to one another:
420+ self . deps . iter_mut ( ) . for_each ( |item| {
421+ item. nullable = true ;
422+ item. null_equality = NullEquality :: NullEqualsNothing ;
423+ } ) ;
436424 }
437425
438426 /// This function ensures that functional dependencies involving uniquely
@@ -469,22 +457,19 @@ pub fn aggregate_functional_dependencies(
469457 aggr_schema : & DFSchema ,
470458) -> FunctionalDependencies {
471459 let mut aggregate_func_dependencies = vec ! [ ] ;
472- let mut aggregate_null_equalities = vec ! [ ] ;
473460 let aggr_input_fields = aggr_input_schema. field_names ( ) ;
474461 let aggr_fields = aggr_schema. fields ( ) ;
475462 // Association covers the whole table:
476463 let target_indices = ( 0 ..aggr_schema. fields ( ) . len ( ) ) . collect :: < Vec < _ > > ( ) ;
477464 // Get functional dependencies of the schema:
478465 let func_dependencies = aggr_input_schema. functional_dependencies ( ) ;
479- for (
480- FunctionalDependence {
481- source_indices,
482- nullable,
483- mode,
484- ..
485- } ,
486- input_null_equality,
487- ) in func_dependencies. iter_with_null_equality ( )
466+ for FunctionalDependence {
467+ source_indices,
468+ nullable,
469+ null_equality,
470+ mode,
471+ ..
472+ } in & func_dependencies. deps
488473 {
489474 // Keep source indices in a `HashSet` to prevent duplicate entries:
490475 let mut new_source_indices = vec ! [ ] ;
@@ -520,24 +505,23 @@ pub fn aggregate_functional_dependencies(
520505 } ;
521506 // All of the composite indices occur in the GROUP BY expression:
522507 if new_source_indices. len ( ) == source_indices. len ( ) {
523- // GROUP BY treats NULL values as equal. When this determinant
524- // covers the complete grouping key, at most one output row exists
525- // for its NULL value too.
508+ // GROUP BY treats NULLs as equal: a determinant covering the
509+ // complete grouping key gets at most one output row per NULL too.
526510 let output_null_equality =
527511 if new_source_indices. len ( ) == group_by_expr_names. len ( ) {
528512 NullEquality :: NullEqualsNull
529513 } else {
530- input_null_equality
514+ * null_equality
531515 } ;
532516 aggregate_func_dependencies. push (
533517 FunctionalDependence :: new (
534518 new_source_indices,
535519 target_indices. clone ( ) ,
536520 * nullable,
537521 )
538- . with_mode ( mode) ,
522+ . with_mode ( mode)
523+ . with_null_equality ( output_null_equality) ,
539524 ) ;
540- aggregate_null_equalities. push ( output_null_equality) ;
541525 }
542526 }
543527
@@ -562,15 +546,13 @@ pub fn aggregate_functional_dependencies(
562546 // Use nullable property of the GROUP BY expression:
563547 aggregate_func_dependencies. push (
564548 FunctionalDependence :: new ( source_indices, target_indices, nullable)
565- . with_mode ( Dependency :: Single ) ,
549+ . with_mode ( Dependency :: Single )
550+ // Grouping collapses NULL keys into a single group:
551+ . with_null_equality ( NullEquality :: NullEqualsNull ) ,
566552 ) ;
567- aggregate_null_equalities. push ( NullEquality :: NullEqualsNull ) ;
568553 }
569554 }
570- FunctionalDependencies :: new_with_null_equalities (
571- aggregate_func_dependencies,
572- aggregate_null_equalities,
573- )
555+ FunctionalDependencies :: new ( aggregate_func_dependencies)
574556}
575557
576558/// Returns target indices, for the determinant keys that are inside
@@ -680,24 +662,16 @@ pub fn get_required_sort_exprs_indices(
680662 } ;
681663
682664 // A sort expression is removable if its value is functionally determined
683- // by fields that already appear earlier in the sort order. A nullable
684- // dependency is only valid here when its determinant treats NULLs as
685- // equal (for example, a GROUP BY key), or no determinant field can be NULL.
686- let removable =
687- dependencies
688- . iter_with_null_equality ( )
689- . any ( |( dependency, null_equality) | {
690- dependency. target_indices . contains ( & field_idx)
691- && dependency
692- . source_indices
693- . iter ( )
694- . all ( |source_idx| known_field_indices. contains ( source_idx) )
695- && ( null_equality == NullEquality :: NullEqualsNull
696- || !dependency. nullable
697- || dependency. source_indices . iter ( ) . all ( |& source_idx| {
698- !schema. field ( source_idx) . is_nullable ( )
699- } ) )
700- } ) ;
665+ // by fields that already appear earlier in the sort order (and the
666+ // dependency remains valid across NULL rows).
667+ let removable = dependencies. deps . iter ( ) . any ( |dependency| {
668+ dependency. target_indices . contains ( & field_idx)
669+ && dependency
670+ . source_indices
671+ . iter ( )
672+ . all ( |source_idx| known_field_indices. contains ( source_idx) )
673+ && dependency. is_valid_across_nulls ( schema)
674+ } ) ;
701675
702676 if removable {
703677 continue ;
0 commit comments