@@ -375,63 +375,19 @@ private static Mask buildMask(Chunk chunk, RowFilter filter, Arena arena) {
375375
376376 /// Walks the filter tree, evaluating each leaf into a per-column [Mask] and collecting them into
377377 /// `out`; the conjuncts of an n-ary `AND` are flattened so [#buildMask] intersects them together.
378+ /// A [RowFilter.Column] already holds the exact compute [Predicate], so the kernel runs it
379+ /// directly against the bound column — no translation seam, since `>=`, `<=` and `<>` are now
380+ /// first-class predicate variants.
378381 private static void collectLeafMasks (Chunk chunk , RowFilter filter , Arena arena , List <Mask > out ) {
379- if (filter instanceof RowFilter .And (var parts )) {
380- for (RowFilter part : parts ) {
381- collectLeafMasks (chunk , part , arena , out );
382+ switch (filter ) {
383+ case RowFilter .And (var parts ) -> {
384+ for (RowFilter part : parts ) {
385+ collectLeafMasks (chunk , part , arena , out );
386+ }
382387 }
383- return ;
388+ case RowFilter .Column (var col , var predicate ) ->
389+ out .add (Compute .filter (chunk .column (col ), predicate , arena ));
384390 }
385- out .add (Compute .filter (chunk .column (leafColumn (filter )), toPredicate (filter ), arena ));
386- }
387-
388- /// The column a leaf [RowFilter] references (never called for an `AND`, which has no single
389- /// column).
390- private static String leafColumn (RowFilter leaf ) {
391- return switch (leaf ) {
392- case RowFilter .Eq (var col , var ignored ) -> col ;
393- case RowFilter .Neq (var col , var ignored ) -> col ;
394- case RowFilter .Gt (var col , var ignored ) -> col ;
395- case RowFilter .Gte (var col , var ignored ) -> col ;
396- case RowFilter .Lt (var col , var ignored ) -> col ;
397- case RowFilter .Lte (var col , var ignored ) -> col ;
398- case RowFilter .IsNull (var col ) -> col ;
399- case RowFilter .IsNotNull (var col ) -> col ;
400- case RowFilter .And ignored ->
401- throw new IllegalArgumentException ("leafColumn called on a composite AND filter" );
402- };
403- }
404-
405- /// Translates a leaf [RowFilter] into the equivalent compute [Predicate]. The compute vocabulary
406- /// has no direct `>=`, `<=`, or `<>`, so each is composed from the ops it does have — all
407- /// null-correct because every component excludes nulls:
408- /// - `>= v` is `= v OR > v`;
409- /// - `<= v` is `= v OR < v`;
410- /// - `<> v` is `< v OR > v`.
411- /// This keeps a `BETWEEN` (which Calcite lowers to `>= a AND <= b`) foldable on a boundary zone
412- /// rather than abandoning it.
413- private static Predicate toPredicate (RowFilter leaf ) {
414- return switch (leaf ) {
415- case RowFilter .Eq (var ignored , var value ) -> new Predicate .Eq (value );
416- case RowFilter .Neq (var ignored , var value ) ->
417- new Predicate .Or (new Predicate .Lt (asComparable (value )), new Predicate .Gt (asComparable (value )));
418- case RowFilter .Gt (var ignored , var value ) -> new Predicate .Gt (value );
419- case RowFilter .Gte (var ignored , var value ) ->
420- new Predicate .Or (new Predicate .Eq (value ), new Predicate .Gt (value ));
421- case RowFilter .Lt (var ignored , var value ) -> new Predicate .Lt (value );
422- case RowFilter .Lte (var ignored , var value ) ->
423- new Predicate .Or (new Predicate .Eq (value ), new Predicate .Lt (value ));
424- case RowFilter .IsNull (var ignored ) -> new Predicate .IsNull ();
425- case RowFilter .IsNotNull (var ignored ) -> new Predicate .IsNotNull ();
426- case RowFilter .And ignored ->
427- throw new IllegalArgumentException ("toPredicate called on a composite AND filter" );
428- };
429- }
430-
431- /// Casts a leaf's value to [Comparable] for the ordering predicates (`< v` / `> v`). The values
432- /// the filter translation produces are always [Long] / [Double] / [String], all comparable.
433- private static Comparable <?> asComparable (Object value ) {
434- return (Comparable <?>) value ;
435391 }
436392
437393 /// Mutable accumulator for a filtered fold, shared by the whole-zone (tier 1) and boundary-zone
@@ -558,8 +514,28 @@ private static Match classify(RowFilter filter, int zone,
558514 }
559515 yield allIn ? Match .IN : Match .BOUNDARY ;
560516 }
561- case RowFilter .Eq (var col , var value ) -> {
562- ArrayStats s = zoneStats .get (col ).get (zone );
517+ case RowFilter .Column (var col , var predicate ) ->
518+ classifyColumn (predicate , zoneStats .get (col ).get (zone ), rowCount );
519+ };
520+ }
521+
522+ /// Classifies one zone against a column-bound [Predicate] from the zone's statistics `s`. The
523+ /// comparison ops carry the same three-valued-logic semantics as before the [RowFilter] /
524+ /// [Predicate] unification: an unrecognised stat shape or a partially-overlapping zone is
525+ /// [Match#BOUNDARY], a zone provably outside the predicate is [Match#OUT], and a zone every row
526+ /// of which matches (which, for a value comparison, also requires the zone to carry no nulls) is
527+ /// [Match#IN]. The composite and range predicates ([Predicate.Between] / [Predicate.And] /
528+ /// [Predicate.Or]) cannot arise from the Calcite translation — `SEARCH` / `BETWEEN` expand to an
529+ /// `AND` of single-leaf columns — so they fall through to [Match#BOUNDARY], which decodes and
530+ /// applies the kernel mask and is therefore always correct.
531+ ///
532+ /// @param predicate the column-bound value-test
533+ /// @param s the zone's statistics for that column
534+ /// @param rowCount the zone's row count
535+ /// @return how the zone relates to the predicate
536+ private static Match classifyColumn (Predicate predicate , ArrayStats s , long rowCount ) {
537+ return switch (predicate ) {
538+ case Predicate .Eq (var value ) -> {
563539 if (uncomparable (s , value )) {
564540 yield Match .BOUNDARY ;
565541 }
@@ -571,8 +547,7 @@ private static Match classify(RowFilter filter, int zone,
571547 }
572548 yield Match .BOUNDARY ;
573549 }
574- case RowFilter .Neq (var col , var value ) -> {
575- ArrayStats s = zoneStats .get (col ).get (zone );
550+ case Predicate .Neq (var value ) -> {
576551 if (uncomparable (s , value )) {
577552 yield Match .BOUNDARY ;
578553 }
@@ -584,16 +559,16 @@ private static Match classify(RowFilter filter, int zone,
584559 }
585560 yield Match .BOUNDARY ;
586561 }
587- case RowFilter .Gt (var col , var value ) -> compare (zoneStats . get ( col ). get ( zone ) , value ,
562+ case Predicate .Gt (var value ) -> compare (s , value ,
588563 (st , v ) -> compareStat (st .max (), v ) <= 0 , (st , v ) -> compareStat (st .min (), v ) > 0 );
589- case RowFilter .Gte (var col , var value ) -> compare (zoneStats . get ( col ). get ( zone ) , value ,
564+ case Predicate .Gte (var value ) -> compare (s , value ,
590565 (st , v ) -> compareStat (st .max (), v ) < 0 , (st , v ) -> compareStat (st .min (), v ) >= 0 );
591- case RowFilter .Lt (var col , var value ) -> compare (zoneStats . get ( col ). get ( zone ) , value ,
566+ case Predicate .Lt (var value ) -> compare (s , value ,
592567 (st , v ) -> compareStat (st .min (), v ) >= 0 , (st , v ) -> compareStat (st .max (), v ) < 0 );
593- case RowFilter .Lte (var col , var value ) -> compare (zoneStats . get ( col ). get ( zone ) , value ,
568+ case Predicate .Lte (var value ) -> compare (s , value ,
594569 (st , v ) -> compareStat (st .min (), v ) > 0 , (st , v ) -> compareStat (st .max (), v ) <= 0 );
595- case RowFilter .IsNull ( var col ) -> {
596- Long nulls = zoneStats . get ( col ). get ( zone ) .nullCount ();
570+ case Predicate .IsNull ignored -> {
571+ Long nulls = s .nullCount ();
597572 if (nulls == null ) {
598573 yield Match .BOUNDARY ;
599574 }
@@ -602,8 +577,8 @@ private static Match classify(RowFilter filter, int zone,
602577 }
603578 yield nulls == 0 ? Match .OUT : Match .BOUNDARY ;
604579 }
605- case RowFilter .IsNotNull ( var col ) -> {
606- Long nulls = zoneStats . get ( col ). get ( zone ) .nullCount ();
580+ case Predicate .IsNotNull ignored -> {
581+ Long nulls = s .nullCount ();
607582 if (nulls == null ) {
608583 yield Match .BOUNDARY ;
609584 }
@@ -612,6 +587,9 @@ private static Match classify(RowFilter filter, int zone,
612587 }
613588 yield nulls == rowCount ? Match .OUT : Match .BOUNDARY ;
614589 }
590+ case Predicate .Between ignored -> Match .BOUNDARY ;
591+ case Predicate .And ignored -> Match .BOUNDARY ;
592+ case Predicate .Or ignored -> Match .BOUNDARY ;
615593 };
616594 }
617595
@@ -917,14 +895,7 @@ private static Optional<RowFilter> toRowFilter(List<RexNode> filters, List<Strin
917895 private static void collectColumns (RowFilter filter , java .util .Set <String > out ) {
918896 switch (filter ) {
919897 case RowFilter .And (var parts ) -> parts .forEach (f -> collectColumns (f , out ));
920- case RowFilter .Eq (var col , var ignored ) -> out .add (col );
921- case RowFilter .Neq (var col , var ignored ) -> out .add (col );
922- case RowFilter .Gt (var col , var ignored ) -> out .add (col );
923- case RowFilter .Gte (var col , var ignored ) -> out .add (col );
924- case RowFilter .Lt (var col , var ignored ) -> out .add (col );
925- case RowFilter .Lte (var col , var ignored ) -> out .add (col );
926- case RowFilter .IsNull (var col ) -> out .add (col );
927- case RowFilter .IsNotNull (var col ) -> out .add (col );
898+ case RowFilter .Column (var col , var ignored ) -> out .add (col );
928899 }
929900 }
930901
0 commit comments