Copilot commented on code in PR #172:
URL: https://github.com/apache/hbase-connectors/pull/172#discussion_r4444964994


##########
spark/hbase-spark/src/main/scala/org/apache/hadoop/hbase/spark/DefaultSource.scala:
##########
@@ -1142,6 +1142,31 @@ class RowKeyFilter(
     result
   }
 
+  // Normalize after the complete filter tree is built, before region pruning 
and Get batching.
+  def normalize(): RowKeyFilter = {
+    val normalizedRanges = new mutable.MutableList[ScanRange]()

Review Comment:
   `mutable.MutableList` is deprecated in modern Scala and is generally 
discouraged. Since this method is newly added/updated, prefer a non-deprecated 
collection (e.g., `scala.collection.mutable.ListBuffer`) for `normalizedRanges` 
to avoid deprecation churn and align with the Spark4 connector implementation.



##########
spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/ScanRange.scala:
##########
@@ -145,6 +145,31 @@ class RowKeyFilter(
   if (currentRange != null) ranges += currentRange
   if (currentPoint != null) points += currentPoint
 
+  // Normalize after the complete filter tree is built, before region pruning 
and Get batching.
+  def normalize(): RowKeyFilter = {
+    val normalizedRanges = new ListBuffer[ScanRange]()
+    ranges
+      .sortWith((left, right) => Bytes.compareTo(left.lowerBound, 
right.lowerBound) < 0)

Review Comment:
   `Bytes.compareTo(left.lowerBound, right.lowerBound)` will throw if either 
`lowerBound` is `null`. Elsewhere in the codebase (e.g., scan planning) 
`lowerBound` can be `null` to represent an unbounded/empty start. Update the 
sort comparator to handle `null` safely (e.g., treat `null` as 
`Array.emptyByteArray` or explicitly order `null` first), so `normalize()` 
cannot NPE on full-scan / unbounded-range filters.



##########
spark/hbase-spark/src/main/scala/org/apache/hadoop/hbase/spark/datasources/HBaseTableScanRDD.scala:
##########
@@ -182,9 +183,10 @@ class HBaseTableScanRDD(
       filter: Option[SparkSQLPushDownFilter],
       columns: Seq[Field]): Scan = {
     val scan = (range.lower, range.upper) match {
-      case (Some(Bound(a, b)), Some(Bound(c, d))) => new Scan(a, c)
+      case (Some(Bound(a, b)), Some(Bound(c, d))) =>
+        new Scan().withStartRow(a, b).withStopRow(c, d)
       case (None, Some(Bound(c, d))) => new Scan(Array[Byte](), c)

Review Comment:
   The `(None, Some(Bound(c, d)))` case ignores the stop-row inclusivity flag 
`d`, which can change query semantics for `<=` / inclusive upper bounds. Use 
the inclusive-aware API for stop rows in this branch as well (e.g., construct a 
`Scan` and apply `withStopRow(c, d)`), to keep behavior consistent with the 
other cases.



##########
spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/ScanRange.scala:
##########
@@ -145,6 +145,31 @@ class RowKeyFilter(
   if (currentRange != null) ranges += currentRange
   if (currentPoint != null) points += currentPoint
 
+  // Normalize after the complete filter tree is built, before region pruning 
and Get batching.
+  def normalize(): RowKeyFilter = {
+    val normalizedRanges = new ListBuffer[ScanRange]()
+    ranges
+      .sortWith((left, right) => Bytes.compareTo(left.lowerBound, 
right.lowerBound) < 0)
+      .foreach {
+        range =>
+          normalizedRanges.lastOption match {
+            case Some(previous) =>
+              val cmp = previous.compareRange(previous.upperBound, 
range.lowerBound)
+              if (cmp > 0 ||
+                (cmp == 0 && (previous.isUpperBoundEqualTo || 
range.isLowerBoundEqualTo))) {
+                previous.mergeUnion(range)
+              } else {
+                normalizedRanges += range
+              }
+            case None => normalizedRanges += range
+          }
+      }
+    ranges = normalizedRanges
+    val seen = new java.util.TreeSet[Array[Byte]](Bytes.BYTES_COMPARATOR)
+    points = points.filter(p => seen.add(p) && 
!ranges.exists(_.containsPoint(p)))

Review Comment:
   After ranges are normalized and made disjoint, pruning points with 
`ranges.exists(_.containsPoint(p))` is O(#points × #ranges). If row-key 
predicates can produce many points/ranges (e.g., large IN-lists combined with 
unions), this may become a hot path. Consider optimizing membership checks 
using the fact that `ranges` are sorted/disjoint (e.g., binary search over 
range boundaries, or a single pass if you also process points in sorted order), 
while preserving the desired point ordering semantics.



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