rangareddy commented on code in PR #19974:
URL: https://github.com/apache/hudi/pull/19974#discussion_r4046361119


##########
hudi-spark-datasource/hudi-spark/src/main/scala/org/apache/spark/sql/hudi/command/procedures/ShowHoodieLogFileRecordsProcedure.scala:
##########
@@ -63,6 +63,9 @@ class ShowHoodieLogFileRecordsProcedure extends BaseProcedure 
with ProcedureBuil
     val filter = getArgValueOrDefault(args, 
parameters(5)).get.asInstanceOf[String]
 
     validateFilter(filter, outputType)
+    // `limit` bounds how many records are read out of the log files, so with 
a filter it has to be lifted
+    // here and reapplied to the matching rows; otherwise the filter only ever 
sees the first `limit`.
+    val scanLimit = if (hasFilter(filter)) Int.MaxValue else limit

Review Comment:
   Good catch, fixed. You are right that this was unbounded: with a filter the 
scan bound was lifted wholesale, so every record of every matched log file 
landed in the on-heap list.
   
   It now filters incrementally as you suggested. Rows go into a small batch, 
`applyFilter` runs per batch, only the matches are retained, and the scan stops 
as soon as `limit` matches have been found, so the heap is bounded by the batch 
plus the matches no matter how much the log files hold. Since `applyFilter` is 
a passthrough when no filter is set, a batch of one reproduces the previous 
bound exactly, which the earlier approach could not promise for the unfiltered 
path.
   
   Covered by a new assertion in `TestHoodieLogFileProcedure` that requests 
each of two records with `limit => 1`; asserting both keeps it independent of 
scan order. Bounding by rows seen rather than rows matched fails it (`Expected 
1, but got 0 ... matching b2`).



##########
hudi-spark-datasource/hudi-spark/src/main/scala/org/apache/spark/sql/hudi/command/procedures/BaseProcedure.scala:
##########
@@ -137,4 +137,15 @@ abstract class BaseProcedure extends Procedure {
       results
     }
   }
+
+  protected def hasFilter(filter: String): Boolean = filter != null && 
filter.trim.nonEmpty
+
+  /**
+   * Filters first and truncates afterwards, so `limit` bounds the matching 
rows rather than the rows the
+   * filter gets to see. Truncating first makes `limit => n, filter => ...` 
return the matches among the
+   * first n rows, which is empty whenever the matches all sit past the 
cut-off.
+   */
+  protected def applyFilterAndLimit(results: Seq[Row], filter: String, schema: 
StructType, limit: Int): Seq[Row] = {
+    applyFilter(results, filter, schema).take(limit)
+  }
 }

Review Comment:
   Done - added `resolveLimit(limit: Option[Any]): Int` to `BaseProcedure` next 
to `hasFilter`, and the three sites now use it.



-- 
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