This is an automated email from the ASF dual-hosted git repository.
virajjasani pushed a commit to branch 5.3
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/5.3 by this push:
new 96323c53a3 PHOENIX-8010 NullPointerException in getOffsetScanner for
point-lookup queries with OFFSET (#2626)
96323c53a3 is described below
commit 96323c53a32d85433b2dee9954709bc2a9fcb8a4
Author: Xavier Fernandis <[email protected]>
AuthorDate: Tue Oct 13 21:38:19 2026 +0530
PHOENIX-8010 NullPointerException in getOffsetScanner for point-lookup
queries with OFFSET (#2626)
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../iterate/NonAggregateRegionScannerFactory.java | 10 ++++--
.../apache/phoenix/end2end/QueryWithOffsetIT.java | 40 ++++++++++++++++++++++
2 files changed, 48 insertions(+), 2 deletions(-)
diff --git
a/phoenix-core-server/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java
b/phoenix-core-server/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java
index 65affd6e79..946e95821f 100644
---
a/phoenix-core-server/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java
+++
b/phoenix-core-server/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java
@@ -389,9 +389,15 @@ public class NonAggregateRegionScannerFactory extends
RegionScannerFactory {
// offset. However, if the region moves after valid row was returned, we
do need to
// set row count to offset because we return valid row only after offset
num of rows
// are skipped.
+ // prevScanStartRowKey is null on the very first scan RPC for scans that
do not go
+ // through ScanRanges.intersectScan(), e.g. the point-lookup fast path in
+ // BaseResultIterators. Such scans are single-RPC and never a mid-scan
continuation,
+ // so no row-count recovery is needed. Guarding against null also avoids a
NPE from
+ // ByteUtil.concat(null, ...) below (see PHOENIX-8010).
if (
- Bytes.compareTo(prevScanStartRowKey, initStartRowKey) != 0 && Bytes
- .compareTo(ByteUtil.concat(prevScanStartRowKey, ByteUtil.ZERO_BYTE),
initStartRowKey) != 0
+ prevScanStartRowKey != null && Bytes.compareTo(prevScanStartRowKey,
initStartRowKey) != 0
+ && Bytes.compareTo(ByteUtil.concat(prevScanStartRowKey,
ByteUtil.ZERO_BYTE),
+ initStartRowKey) != 0
) {
iterator.setRowCountToOffset();
}
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java
index 65ec34ff08..aee144740e 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java
@@ -213,6 +213,46 @@ public class QueryWithOffsetIT extends
ParallelStatsDisabledIT {
conn.close();
}
+ @Test
+ public void testPointLookupWithOffset() throws SQLException {
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ createTestTable(getUrl(), ddl);
+ initTableValues(conn);
+ updateStatistics(conn);
+ // Point lookup (full primary key pinned) combined with OFFSET. Prior to
+ // PHOENIX-8010 this threw a server-side NullPointerException from
+ // ByteUtil.concat(null, ...) in
NonAggregateRegionScannerFactory.getOffsetScanner,
+ // because the point-lookup fast path never sets the
SCAN_ACTUAL_START_ROW scan
+ // attribute, leaving it null on the first (and only) scan RPC.
+ // The single matching row must be returned for OFFSET 0.
+ String pointLookupOffsetZero = "SELECT t_id, k1, k2 FROM " + tableName
+ + " WHERE t_id = 'c' AND k1 = 2 AND k2 = 3 LIMIT 5 OFFSET 0";
+ try (ResultSet rs =
conn.createStatement().executeQuery(pointLookupOffsetZero)) {
+ assertTrue(rs.next());
+ assertEquals("c", rs.getString(1));
+ assertEquals(2, rs.getInt(2));
+ assertEquals(3, rs.getInt(3));
+ assertFalse(rs.next());
+ }
+ // A non-zero OFFSET skips the only matching row, yielding an empty
result set
+ // (and must still not crash).
+ String pointLookupOffsetOne = "SELECT t_id FROM " + tableName
+ + " WHERE t_id = 'c' AND k1 = 2 AND k2 = 3 LIMIT 5 OFFSET 1";
+ try (ResultSet rs =
conn.createStatement().executeQuery(pointLookupOffsetOne)) {
+ assertFalse(rs.next());
+ }
+ // The reverse-scan variant exercises the same crash site.
+ String pointLookupReversed = "SELECT t_id FROM " + tableName
+ + " WHERE t_id = 'c' AND k1 = 2 AND k2 = 3 ORDER BY t_id DESC LIMIT 5
OFFSET 0";
+ try (ResultSet rs =
conn.createStatement().executeQuery(pointLookupReversed)) {
+ assertTrue(rs.next());
+ assertEquals("c", rs.getString(1));
+ assertFalse(rs.next());
+ }
+ }
+ }
+
@Test
public void testMetaDataWithOffset() throws SQLException {
Connection conn;