Skip to content

Implementing DataSource V2 Read Path - #162

Open
wchevreuil wants to merge 10 commits into
apache:HBASE-30189from
wchevreuil:HBASE-30189
Open

Implementing DataSource V2 Read Path#162
wchevreuil wants to merge 10 commits into
apache:HBASE-30189from
wchevreuil:HBASE-30189

Conversation

@wchevreuil

Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements the Spark 4 DataSource V2 read path for HBase, including filter pushdown, partition planning, and row conversion.

Changes:

  • Adds the V2 provider, table, scan, batch, partition, and reader pipeline.
  • Ports row-key range and predicate handling.
  • Adds unit and mini-cluster integration tests.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
ScanRange.scala Adds row-key range operations.
HBaseTableProvider.scala Adds the V2 provider entry point.
HBaseTable.scala Declares table schema and read capability.
HBaseScanBuilder.scala Negotiates filters and projections.
HBaseScan.scala Builds the logical HBase scan.
HBaseBatch.scala Plans region-based input partitions.
HBaseInputPartition.scala Defines serializable partitions.
HBasePartitionReaderFactory.scala Creates partition readers.
HBasePartitionReader.scala Executes scans and creates rows.
ScanRangeSuite.scala Tests range operations.
HBaseScanBuilderSuite.scala Tests filter and column pushdown.
HBaseTableCatalogSuite.scala Tests catalog parsing.
HBaseTableProviderSuite.scala Tests end-to-end reads.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@taklwu taklwu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may you fix the commit title with JIRA prefix HBASE-30189 ?

.orElse(intersectedPoints.lastOption.map(Utils.incrementByteArray))
.orElse(region.end)
.orNull
Some(HBaseInputPartition(region.index, startRow, stopRow): InputPartition)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a behavior difference from V1 that HBaseInputPartition that uses HBasePartitionReader.scala are scanning instead of Get , do you think this is good ? if not , please try to align what Spark3 does with Get.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use Get too. Changing this in the next commit.

extends Batch
with Logging {

override def planInputPartitions(): Array[InputPartition] = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we may need to support getPreferredLocations like V1 does , you can have it in the future PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is defined in the RDD class, which is the model used in V1. V2 uses the Batch model, so there's no RDD subclassing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented the "preferredLocations" method in the InputPartition.

@wchevreuil

Copy link
Copy Markdown
Contributor Author

may you fix the commit title with JIRA prefix HBASE-30189 ?

I'm actually targeting this PR to the HBASE-30189 branch, which already has two previous commits related to this HBASE-30189. The idea was to split HBASE-30189 into several PRs for ease of review. After all PRs are in HBASE-30189, I'm going to merge it into master as a single commit for HBASE-30189.

@wchevreuil
wchevreuil requested review from taklwu and a balanced review from Copilot September 8, 2026 16:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 8 comments.

@Apache-HBase

Copy link
Copy Markdown

(!) A patch to the testing environment has been detected.
Re-executing against the patched versions to perform further tests.
The console is at https://ci-hbase.apache.org/job/HBase-Connectors-PreCommit/job/PR-162/7/console in case of problems.

@Apache-HBase

Copy link
Copy Markdown

(!) A patch to the testing environment has been detected.
Re-executing against the patched versions to perform further tests.
The console is at https://ci-hbase.apache.org/job/HBase-Connectors-PreCommit/job/PR-162/8/console in case of problems.

wchevreuil and others added 7 commits September 10, 2026 10:07
…job (apache#164)

Signed-off-by: Tak Lon (Stephen) Wu <taklwu@apache.org>
Signed-off-by: Dávid Paksy <paksyd@apache.org>
Reviewed-by: Kevin Geiszler <kevin.j.geiszler@gmail.com>
Co-authored-by: Claude Code (claude-opus-4-6) <no-reply@anthropic.com>
Signed-off-by: Peter Somogyi <psomogyi@apache.org>
…emas (apache#160)

Co-authored-by: Claude Code (claude-opus-4-6) <no-reply@anthropic.com>
    
Signed-off-by: Peter Somogyi <psomogyi@apache.org>
@Apache-HBase

Copy link
Copy Markdown

(!) A patch to the testing environment has been detected.
Re-executing against the patched versions to perform further tests.
The console is at https://ci-hbase.apache.org/job/HBase-Connectors-PreCommit/job/PR-162/10/console in case of problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Composite keys, unions, null handling, and configuration propagation still contain correctness or scalability defects.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/HBasePartitionReader.scala:198

  • The same missing-qualifier problem affects point reads: a Get restricted to the IsNull qualifier returns an empty Result when that qualifier is absent, and line 202 drops it before the predicate can accept it. For pushed expressions that can match a missing cell, do not restrict the Get to only those qualifiers (or otherwise fetch an existence column).
        scanFields.foreach { f =>
          g.addColumn(f.cfBytes, f.colBytes)
        }
  • Files reviewed: 17/17 changed files
  • Comments generated: 8
  • Review effort level: Balanced

@Apache-HBase

Copy link
Copy Markdown

(!) A patch to the testing environment has been detected.
Re-executing against the patched versions to perform further tests.
The console is at https://ci-hbase.apache.org/job/HBase-Connectors-PreCommit/job/PR-162/11/console in case of problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The read path has unresolved serialization, filter encoding, partial-result, configuration, and Catalyst conversion defects.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/HBasePartitionReader.scala:177

  • A positive HBase Scan batch size allows one logical row to be returned as multiple partial Results. This reader treats every Result as a complete Spark row, so hbase.spark.query.batchsize can produce duplicate rows with nulls for cells delivered in another batch. Either coalesce partial results (using mayHaveMoreCellsInRow) before exposing them or avoid setting a cell batch size.

spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/HBasePartitionReader.scala:285

  • Complex values are still returned in external Scala form here. SchemaConverters.createConverterToSQL produces Row, Seq, and Map for struct/array/map fields, but GenericInternalRow requires InternalRow, ArrayData, and MapData; reading an Avro-backed complex column will therefore fail or expose an invalid Catalyst value. Use Spark's recursive Catalyst converter rather than handling only scalar special cases.
      case dt: DecimalType =>
        Decimal(value.asInstanceOf[java.math.BigDecimal], dt.precision, dt.scale)
      case _ => value

spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/HBaseScanBuilder.scala:60

  • This ignores the documented global SparkConf fallback (HBaseSparkConf says settings may be global or per-table). Consequently spark.conf.set("hbase.spark.pushdown.columnfilter", "false") has no effect unless the option is repeated on every read, and filters are still reported as fully pushed. Preserve per-read precedence but fall back to the active SparkConf as the Spark 3 path does.
    val usePushDown = properties
      .get(HBaseSparkConf.PUSHDOWN_COLUMN_FILTER)
      .map(_.toBoolean)
      .getOrElse(HBaseSparkConf.DEFAULT_PUSHDOWN_COLUMN_FILTER)
  • Files reviewed: 17/17 changed files
  • Comments generated: 2
  • Review effort level: Balanced

@wchevreuil
wchevreuil requested a balanced review from Copilot September 11, 2026 15:21
@Apache-HBase

Copy link
Copy Markdown

(!) A patch to the testing environment has been detected.
Re-executing against the patched versions to perform further tests.
The console is at https://ci-hbase.apache.org/job/HBase-Connectors-PreCommit/job/PR-162/12/console in case of problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

val endKeys = keys.getSecond

val regions = startKeys.zip(endKeys).zipWithIndex.map { case ((start, end), idx) =>
HBaseRegion(idx, Some(start), Some(end),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a valid issue. The potential problem is addressed in Range.apply(region) code, where empty keys are converted to None (see Bound.scala lines #36-44).

Comment on lines +88 to +95
if (compareRange(lowerBound, other.lowerBound) < 0 ||
compareRange(upperBound, other.upperBound) < 0) {
leftRange = this
rightRange = other
} else {
leftRange = other
rightRange = this
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would still pass the overlaps check which then merges the ScanRanges results accordingly. It copies the logic from spark3 module in DefaultSource.scala. I would suggest optimisation work to be done separately.

Comment on lines +90 to +102
private val resultIterator: Iterator[Result] = {
val scanIterators = partition.scanRanges.map { range =>
val scanner = buildScanner(range)
scanners += scanner
scannerToIterator(scanner)
}
val getIterator = if (partition.points.nonEmpty) {
buildGets(partition.points)
} else {
Iterator.empty
}
scanIterators.foldLeft(Iterator.empty: Iterator[Result])(_ ++ _) ++ getIterator
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pre-existing spark3 module logic, and in practice, scan ranges intersections are very few, so this doesn't present a real performance problem.

@wchevreuil

Copy link
Copy Markdown
Contributor Author

Hey @taklwu , I believe I have now addressed all copilot reviews that were indeed valid. Can you give an extra look into this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants