voonhous commented on code in PR #20526:
URL: https://github.com/apache/hudi/pull/20526#discussion_r4479703676


##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/spark/sql/hudi/dataskipping/StatsFilterCompiler.scala:
##########
@@ -64,10 +64,12 @@ object StatsFilterCompiler {
     case leaf => compileLeaf(leaf, source)
   }
 
-  // Stages 2 to 4, for one leaf
+  // Stages 2 to 4, for one leaf. A verdict over a date rendering also keeps 
the files spanning a local date
+  // reversal ([[LocalDateReversalGuard]])
   private def compileLeaf(e: Expression, source: RangeSource): Expression =
     Leaf.parse(e)
       .flatMap(l => TermRange.derive(l.term, source).map(r => Verdict(l, r)))
+      .flatMap(LocalDateReversalGuard(e, source, _))

Review Comment:
   This is blocked by #19474.
   
   `LocalDateReversalGuard` runs between `Verdict` and `keepOnNull`, which adds 
a fifth stage to the four from #20527 (Shape, Leaf, Range, Verdict). It also 
re-scans the raw leaf for `Cast` / `DateFormatClass` / `FromUnixTime`, so a 
rule arm added later that renders a local date would skip the guard silently.
   
   The NOTE is right that a `fileGuard` ends pruning on master today. #19474 
lifts that: a guarded chain still derives a range, and `keepOnNull` keeps the 
file when a guarded bound is NULL. Once it lands, could we move this into the 
Range stage as a `fileGuard` on the rendering arms and drop the extra stage? 
Rough shape on top of #19474 (names are placeholders):
   
   ```scala
   // RangeRule.scala: combine per-file guards; None stays "unguarded"
   type Guard = TermRange => Expression
   def andGuards(a: Option[Guard], b: Option[Guard]): Option[Guard] = (a, b) 
match {
     case (Some(x), Some(y)) => Some(r => And(x(r), y(r)))
     case _ => a.orElse(b)
   }
   
   // rules/DateTimeOrdering.scala (#19474), next to isFixedOffsetZone
   /** Instants at which the zone's local date runs backwards; None if its 
current rules keep doing it. */
   def localDateReversals(timeZoneId: Option[String]): Option[Seq[Instant]] = 
... // today's localDateReversals, cached
   
   val micros: Instant => Literal = i => 
Literal(DateTimeUtils.instantToMicros(i), TimestampType)
   
   /** File guard: [lo, hi] spans none of the instants. None when there are 
none, so the step stays unguarded. */
   def noLocalDateReversal(instants: Seq[Instant], toLiteral: Instant => 
Literal): Option[Guard] =
     if (instants.isEmpty) None
     else Some(r => instants.map(toLiteral)
       .map(t => Not(And(LessThan(r.lo, t), GreaterThanOrEqual(r.hi, t))))
       .reduce[Expression](And))
   
   // rules/CastRule.scala: ahead of the generic arm, which accepts 
(TimestampType, DateType) unguarded today
   case Cast(child, DateType, timeZoneId, _) if hasDataType(child) && 
child.dataType == TimestampType =>
     localDateReversals(timeZoneId).toSeq.map { instants =>
       Step(term, child, Increasing, fileGuard = noLocalDateReversal(instants, 
micros), partial = false)
     }
   
   // rules/DateTimeFormatRule.scala: next to the existing formattableInstants 
guard
   case DateFormatClass(timestamp, format, timeZoneId)
     if dataTypeOf(timestamp).contains(TimestampType) && 
DateTimeOrdering.formatPreservesOrder(format) =>
     localDateReversals(timeZoneId).toSeq.map { instants =>
       Step(term, timestamp, Increasing,
         fileGuard = andGuards(Some(formattableInstants), 
noLocalDateReversal(instants, micros)), partial = false)
     }
   // FromUnixTime: the same, with epoch seconds and formattableSeconds
   ```
   
   With this, `RuntimeReplaceable` handling, `rebind` and `TryEval` come from 
the existing stages; #19474 already wraps partial bounds in `TryEval`.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to