This is an automated email from the ASF dual-hosted git repository.
tkhurana 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 8bb13a5029 PHOENIX-7748 Empty column cell is not returned when scan
has both EmptyColumnOnlyFilter and DistinctPrefixFilter (#2353)
8bb13a5029 is described below
commit 8bb13a502900ce1b3cc0c79fd89fd439f3666f69
Author: tkhurana <[email protected]>
AuthorDate: Tue Jan 27 14:49:18 2026 -0800
PHOENIX-7748 Empty column cell is not returned when scan has both
EmptyColumnOnlyFilter and DistinctPrefixFilter (#2353)
---
.../phoenix/filter/EmptyColumnOnlyFilter.java | 19 +++++++-
.../org/apache/phoenix/end2end/EmptyColumnIT.java | 57 ++++++++++++++++++++++
.../end2end/index/GlobalIndexCheckerIT.java | 41 ++++++++++++++++
3 files changed, 116 insertions(+), 1 deletion(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/filter/EmptyColumnOnlyFilter.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/filter/EmptyColumnOnlyFilter.java
index 03e0168c3b..a5d78112c2 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/filter/EmptyColumnOnlyFilter.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/filter/EmptyColumnOnlyFilter.java
@@ -39,6 +39,7 @@ public class EmptyColumnOnlyFilter extends FilterBase
implements Writable {
private byte[] emptyCQ;
private boolean found = false;
private boolean first = true;
+ private Cell emptyColumnCell = null;
public EmptyColumnOnlyFilter() {
}
@@ -54,6 +55,7 @@ public class EmptyColumnOnlyFilter extends FilterBase
implements Writable {
public void reset() throws IOException {
found = false;
first = true;
+ emptyColumnCell = null;
}
// No @Override for HBase 3 compatibility
@@ -68,6 +70,7 @@ public class EmptyColumnOnlyFilter extends FilterBase
implements Writable {
}
if (ScanUtil.isEmptyColumn(cell, emptyCF, emptyCQ)) {
found = true;
+ emptyColumnCell = cell;
return ReturnCode.INCLUDE;
}
if (first) {
@@ -79,8 +82,22 @@ public class EmptyColumnOnlyFilter extends FilterBase
implements Writable {
@Override
public void filterRowCells(List<Cell> kvs) throws IOException {
- if (kvs.size() > 1) {
+ if (kvs.size() > 2) {
+ throw new IOException("EmptyColumnOnlyFilter got unexpected cells: " +
kvs.size());
+ } else if (kvs.size() == 2) {
+ // remove the first cell and only return the empty column cell
kvs.remove(0);
+ } else if (kvs.size() == 1) {
+ // we only have 1 cell, check if it is the empty column cell or not
+ // since the empty column cell could have been excluded by another
filter like the
+ // DistinctPrefixFilter.
+ Cell cell = kvs.get(0);
+ if (found && !ScanUtil.isEmptyColumn(cell, emptyCF, emptyCQ)) {
+ // we found the empty cell, but it was not included so replace the
existing cell
+ // with the empty column cell
+ kvs.remove(0);
+ kvs.add(emptyColumnCell);
+ }
}
}
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/EmptyColumnIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/EmptyColumnIT.java
index 328028ecb2..5e0033bb4f 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/EmptyColumnIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/EmptyColumnIT.java
@@ -31,10 +31,14 @@ import static
org.apache.phoenix.query.PhoenixTestBuilder.SchemaBuilder.TenantVi
import static
org.apache.phoenix.query.PhoenixTestBuilder.SchemaBuilder.TenantViewOptions;
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Random;
@@ -46,13 +50,17 @@ import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.jdbc.PhoenixResultSet;
import org.apache.phoenix.query.PhoenixTestBuilder.BasicDataWriter;
import org.apache.phoenix.query.PhoenixTestBuilder.DataSupplier;
import org.apache.phoenix.query.PhoenixTestBuilder.DataWriter;
import org.apache.phoenix.query.PhoenixTestBuilder.SchemaBuilder;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.util.EnvironmentEdgeManager;
import org.apache.phoenix.util.IndexUtil;
+import org.apache.phoenix.util.ManualEnvironmentEdge;
+import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.SchemaUtil;
import org.apache.phoenix.util.TestUtil;
import org.junit.Ignore;
@@ -703,6 +711,55 @@ public class EmptyColumnIT extends ParallelStatsDisabledIT
{
}
}
+ /**
+ * Test that the empty column cell is returned by the scan when there is a
DistinctPrefixFilter
+ * and an EmptyColumnOnlyFilter. If there is no empty column cell returned
in the scan then TTL
+ * masking logic can break.
+ */
+ @Test
+ public void testMaskingWithDistinctPrefixFilter() throws Exception {
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ int ttl = 10;
+ String dataTableName = generateUniqueName();
+ // not using column encoding so that we use EmptyColumnOnlyFilter
+ String ddl = "create table " + dataTableName + " (id1 varchar(10) not
null , "
+ + "id2 varchar(10) not null , val1 varchar(10), val2 varchar(10) "
+ + "constraint PK primary key (id1, id2)) COLUMN_ENCODED_BYTES=0, TTL="
+ ttl;
+ conn.createStatement().execute(ddl);
+
+ String[] expectedValues = { "val1_1", "val1_2", "val1_3", "val1_4" };
+ String dml = "UPSERT INTO " + dataTableName + " VALUES(?, ?, ?, ?)";
+ PreparedStatement ps = conn.prepareStatement(dml);
+ for (int id1 = 0; id1 < 5; ++id1) {
+ ps.setString(1, "id1_" + id1);
+ for (int id2 = 0; id2 < 5; ++id2) {
+ ps.setString(2, "id2_" + id2);
+ ps.setString(3, "val1_" + id1 % 2);
+ ps.setString(4, "val2_" + id2 % 2);
+ ps.executeUpdate();
+ }
+ }
+ conn.commit();
+ try {
+ ManualEnvironmentEdge injectEdge = new ManualEnvironmentEdge();
+ // expire the rows
+ injectEdge.setValue(EnvironmentEdgeManager.currentTimeMillis() + ttl *
1000 + 2);
+ EnvironmentEdgeManager.injectEdge(injectEdge);
+ String distinctQuery = "SELECT DISTINCT id1 FROM " + dataTableName;
+ try (ResultSet rs =
conn.createStatement().executeQuery(distinctQuery)) {
+ PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class);
+ String explainPlan =
QueryUtil.getExplainPlan(prs.getUnderlyingIterator());
+ assertTrue(explainPlan.contains("SERVER FILTER BY EMPTY COLUMN
ONLY"));
+ assertTrue(explainPlan.contains("SERVER DISTINCT PREFIX FILTER
OVER"));
+ // all the rows should have been masked
+ assertFalse(rs.next());
+ }
+ } finally {
+ EnvironmentEdgeManager.reset();
+ }
+ }
+ }
+
private void upsertDataAndRunValidations(int numRowsToUpsert,
ExpectedTestResults expectedTestResults, DataWriter dataWriter,
SchemaBuilder schemaBuilder,
List<Integer> overriddenColumnsPositions) throws Exception {
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
index ee9acb4aef..0abe583a18 100644
---
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
+++
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
@@ -42,6 +42,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
+import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
@@ -64,6 +65,7 @@ import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.ReadOnlyProps;
import org.apache.phoenix.util.TestUtil;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -1661,6 +1663,45 @@ public class GlobalIndexCheckerIT extends BaseTest {
}
}
+ @Test
+ public void testWithDistinctPrefixFilter() throws Exception {
+ Assume.assumeTrue(async == false);
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String dataTableName = generateUniqueName();
+ String indexTableName = generateUniqueName();
+ String ddl = "create table " + dataTableName + " (id varchar(10) not
null primary key, "
+ + "val1 varchar(10), val2 varchar(10), val3 varchar(10))" +
tableDDLOptions;
+ conn.createStatement().execute(ddl);
+ ddl = "create index " + indexTableName + " on " + dataTableName
+ + " (val1) include (val2, val3)" + this.indexDDLOptions;
+ conn.createStatement().execute(ddl);
+ String[] expectedValues = { "val1_1", "val1_2", "val1_3", "val1_4" };
+ int rowCount = 20;
+ String dml = "UPSERT INTO " + dataTableName + " VALUES(?, ?, ?, ?)";
+ PreparedStatement ps = conn.prepareStatement(dml);
+ for (int id = 0; id < rowCount; ++id) {
+ ps.setString(1, "id_" + id);
+ ps.setString(2, expectedValues[id % expectedValues.length]);
+ ps.setString(3, "val2_" + id % 2);
+ ps.setString(4, "val3");
+ ps.executeUpdate();
+ }
+ conn.commit();
+ String distinctQuery = "SELECT DISTINCT val1 FROM " + dataTableName;
+ try (ResultSet rs = conn.createStatement().executeQuery(distinctQuery)) {
+ PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class);
+ String explainPlan =
QueryUtil.getExplainPlan(prs.getUnderlyingIterator());
+ assertTrue(explainPlan.contains(indexTableName));
+ assertTrue(explainPlan.contains("SERVER DISTINCT PREFIX FILTER OVER"));
+ List actualValues = Lists.newArrayList();
+ while (rs.next()) {
+ actualValues.add(rs.getString(1));
+ }
+ assertEquals(Arrays.asList(expectedValues), actualValues);
+ }
+ }
+ }
+
public static void commitWithException(Connection conn) {
try {
conn.commit();