This is an automated email from the ASF dual-hosted git repository.
vjasani 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 ec1e01dc41 PHOENIX-7760 Incorrect scan range for RVC with overlapping
constraints (#2367)
ec1e01dc41 is described below
commit ec1e01dc41fd12560344dc86b0a16e9d402c79c2
Author: Viraj Jasani <[email protected]>
AuthorDate: Mon Feb 9 21:37:04 2026 -0800
PHOENIX-7760 Incorrect scan range for RVC with overlapping constraints
(#2367)
---
.../org/apache/phoenix/compile/WhereOptimizer.java | 16 ++-
.../phoenix/end2end/RowValueConstructorIT.java | 108 +++++++++++++++++++++
2 files changed, 121 insertions(+), 3 deletions(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
index 9f8e3f14c0..22a59e3487 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
@@ -1571,20 +1571,30 @@ public class WhereOptimizer {
byte[] lowerRange = result.getLowerRange();
byte[] clippedLowerRange = lowerRange;
byte[] fullLowerRange = otherRange.getLowerRange();
+ boolean fullLowerRangeUsed = false;
if (!result.lowerUnbound() && Bytes.startsWith(fullLowerRange,
clippedLowerRange)) {
lowerRange = fullLowerRange;
+ fullLowerRangeUsed = true;
}
byte[] upperRange = result.getUpperRange();
byte[] clippedUpperRange = upperRange;
byte[] fullUpperRange = otherRange.getUpperRange();
- if (!result.lowerUnbound() && Bytes.startsWith(fullUpperRange,
clippedUpperRange)) {
+ boolean fullUpperRangeUsed = false;
+ if (!result.upperUnbound() && Bytes.startsWith(fullUpperRange,
clippedUpperRange)) {
upperRange = fullUpperRange;
+ fullUpperRangeUsed = true;
}
if (lowerRange == clippedLowerRange && upperRange == clippedUpperRange) {
return result;
}
- return KeyRange.getKeyRange(lowerRange, result.isLowerInclusive(),
upperRange,
- result.isUpperInclusive());
+ // When we restore the full range bytes from otherRange, we must also use
+ // the inclusivity from otherRange. Otherwise, an exclusive bound would
incorrectly
+ // become inclusive (PHOENIX-7760).
+ boolean lowerInclusive =
+ fullLowerRangeUsed ? otherRange.isLowerInclusive() :
result.isLowerInclusive();
+ boolean upperInclusive =
+ fullUpperRangeUsed ? otherRange.isUpperInclusive() :
result.isUpperInclusive();
+ return KeyRange.getKeyRange(lowerRange, lowerInclusive, upperRange,
upperInclusive);
}
/**
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
index 9997badea7..f2e4a2faa6 100644
---
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
+++
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
@@ -2166,4 +2166,112 @@ public class RowValueConstructorIT extends
ParallelStatsDisabledIT {
PreparedStatement stmt2 = tenantConn.prepareStatement(tenantViewDdl);
stmt2.execute();
}
+
+ @Test
+ public void testRVCOverlappingKeyRange() throws Exception {
+ String tableName = generateUniqueName();
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ conn.createStatement()
+ .execute("CREATE TABLE " + tableName + " (" + "hk VARCHAR NOT NULL, "
+ + "sk VARCHAR NOT NULL, " + "ihk VARCHAR NOT NULL, " + "isk VARCHAR
NOT NULL, "
+ + "data VARCHAR " + "CONSTRAINT pk PRIMARY KEY (hk, sk, ihk, isk))");
+
+ PreparedStatement upsert = conn.prepareStatement(
+ "UPSERT INTO " + tableName + " (hk, sk, ihk, isk, data) VALUES (?, ?,
?, ?, ?)");
+ for (int i = 1; i <= 20; i++) {
+ String pad = String.format("%02d", i);
+ upsert.setString(1, "hk");
+ upsert.setString(2, "sk100");
+ upsert.setString(3, "idx");
+ upsert.setString(4, "isk" + pad);
+ upsert.setString(5, "data" + pad);
+ upsert.execute();
+ }
+ conn.commit();
+
+ String query = "SELECT * FROM " + tableName + " WHERE hk = 'hk' AND "
+ + "(sk, ihk, isk) > ('sk100', 'idx', 'isk11') ORDER BY sk, ihk, isk
LIMIT 5";
+ assertValues1(conn, query);
+
+ query = "SELECT * FROM " + tableName + " WHERE hk = 'hk' AND sk <=
'sk200' "
+ + "AND (sk, ihk, isk) > ('sk100', 'idx', 'isk11') ORDER BY sk, ihk,
isk" + " LIMIT 5";
+ assertValues1(conn, query);
+
+ query =
+ "SELECT * FROM " + tableName + " WHERE hk = 'hk'" + " AND sk <=
'sk200' AND sk >= 'sk1'"
+ + " AND (sk, ihk, isk) > ('sk100', 'idx', 'isk11')" + " ORDER BY sk,
ihk, isk LIMIT 5";
+ assertValues1(conn, query);
+
+ query = "SELECT * FROM " + tableName + " WHERE hk = 'hk' AND sk >=
'sk000' "
+ + "AND (sk, ihk, isk) >= ('sk100', 'idx', 'isk12') "
+ + "AND (sk, ihk, isk) < ('sk100', 'idx', 'isk17') ORDER BY sk, ihk,
isk";
+ assertValues1(conn, query);
+
+ query = "SELECT * FROM " + tableName + " WHERE hk = 'hk' AND sk >=
'sk000' "
+ + "AND (sk, ihk, isk) > ('sk100', 'idx', 'isk11') "
+ + "AND (sk, ihk, isk) < ('sk100', 'idx', 'isk17') ORDER BY sk, ihk,
isk";
+ assertValues1(conn, query);
+
+ query = "SELECT * FROM " + tableName + " WHERE hk = 'hk' AND sk >=
'sk000' "
+ + "AND (sk, ihk, isk) < ('sk100', 'idx', 'isk17') ORDER BY sk, ihk,
isk";
+ assertValues2(conn, query);
+
+ query = "SELECT hk, sk, ihk, isk FROM " + tableName
+ + " WHERE hk = 'hk' AND sk <= 'sk200' AND (sk, ihk, isk) < ('sk100',
'idx', 'isk11')"
+ + " ORDER BY hk ASC, sk DESC, ihk DESC, isk DESC LIMIT 5";
+ assertValues3(conn, query);
+ }
+ }
+
+ private static void assertValues1(Connection conn, String query) throws
SQLException {
+ try (Statement stmt = conn.createStatement()) {
+ ResultSet rs = stmt.executeQuery(query);
+ List<String> results = Lists.newArrayList();
+ while (rs.next()) {
+ results.add(rs.getString("isk"));
+ }
+ assertEquals(5, results.size());
+ assertFalse("Should not include isk11", results.contains("isk11"));
+ assertEquals("isk12", results.get(0));
+ assertEquals("isk13", results.get(1));
+ assertEquals("isk14", results.get(2));
+ assertEquals("isk15", results.get(3));
+ assertEquals("isk16", results.get(4));
+ }
+ }
+
+ private static void assertValues2(Connection conn, String query) throws
SQLException {
+ try (Statement stmt = conn.createStatement()) {
+ ResultSet rs = stmt.executeQuery(query);
+ List<String> results = Lists.newArrayList();
+ while (rs.next()) {
+ results.add(rs.getString("isk"));
+ }
+ assertEquals(16, results.size());
+ assertTrue("Should not include isk17-isk20 range",
!results.contains("isk17")
+ && !results.contains("isk18") && !results.contains("isk19") &&
!results.contains("isk20"));
+ assertEquals("isk01", results.get(0));
+ assertEquals("isk16", results.get(15));
+ }
+ }
+
+ private static void assertValues3(Connection conn, String query) throws
SQLException {
+ try (Statement stmt = conn.createStatement()) {
+ ResultSet rs = stmt.executeQuery(query);
+ List<String> results = Lists.newArrayList();
+ while (rs.next()) {
+ results.add(rs.getString("isk"));
+ }
+ assertEquals(5, results.size());
+ assertFalse(results.contains("isk11"));
+ assertEquals("isk10", results.get(0));
+ assertEquals("isk09", results.get(1));
+ assertEquals("isk08", results.get(2));
+ assertEquals("isk07", results.get(3));
+ assertEquals("isk06", results.get(4));
+ }
+ }
+
}