Skip to content

Commit 6230eb0

Browse files
authored
PHOENIX-7748 Empty column cell is not returned when scan has both EmptyColumnOnlyFilter and DistinctPrefixFilter (#2353)
1 parent aecd1f5 commit 6230eb0

3 files changed

Lines changed: 116 additions & 1 deletion

File tree

phoenix-core-client/src/main/java/org/apache/phoenix/filter/EmptyColumnOnlyFilter.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class EmptyColumnOnlyFilter extends FilterBase implements Writable {
3939
private byte[] emptyCQ;
4040
private boolean found = false;
4141
private boolean first = true;
42+
private Cell emptyColumnCell = null;
4243

4344
public EmptyColumnOnlyFilter() {
4445
}
@@ -54,6 +55,7 @@ public EmptyColumnOnlyFilter(byte[] emptyCF, byte[] emptyCQ) {
5455
public void reset() throws IOException {
5556
found = false;
5657
first = true;
58+
emptyColumnCell = null;
5759
}
5860

5961
// No @Override for HBase 3 compatibility
@@ -68,6 +70,7 @@ public ReturnCode filterCell(final Cell cell) throws IOException {
6870
}
6971
if (ScanUtil.isEmptyColumn(cell, emptyCF, emptyCQ)) {
7072
found = true;
73+
emptyColumnCell = cell;
7174
return ReturnCode.INCLUDE;
7275
}
7376
if (first) {
@@ -79,8 +82,22 @@ public ReturnCode filterCell(final Cell cell) throws IOException {
7982

8083
@Override
8184
public void filterRowCells(List<Cell> kvs) throws IOException {
82-
if (kvs.size() > 1) {
85+
if (kvs.size() > 2) {
86+
throw new IOException("EmptyColumnOnlyFilter got unexpected cells: " + kvs.size());
87+
} else if (kvs.size() == 2) {
88+
// remove the first cell and only return the empty column cell
8389
kvs.remove(0);
90+
} else if (kvs.size() == 1) {
91+
// we only have 1 cell, check if it is the empty column cell or not
92+
// since the empty column cell could have been excluded by another filter like the
93+
// DistinctPrefixFilter.
94+
Cell cell = kvs.get(0);
95+
if (found && !ScanUtil.isEmptyColumn(cell, emptyCF, emptyCQ)) {
96+
// we found the empty cell, but it was not included so replace the existing cell
97+
// with the empty column cell
98+
kvs.remove(0);
99+
kvs.add(emptyColumnCell);
100+
}
84101
}
85102
}
86103

phoenix-core/src/it/java/org/apache/phoenix/end2end/EmptyColumnIT.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,14 @@
3131
import static org.apache.phoenix.query.PhoenixTestBuilder.SchemaBuilder.TenantViewOptions;
3232
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
3333
import static org.junit.Assert.assertEquals;
34+
import static org.junit.Assert.assertFalse;
35+
import static org.junit.Assert.assertTrue;
3436

3537
import java.io.IOException;
3638
import java.sql.Connection;
3739
import java.sql.DriverManager;
40+
import java.sql.PreparedStatement;
41+
import java.sql.ResultSet;
3842
import java.sql.SQLException;
3943
import java.util.List;
4044
import java.util.Random;
@@ -46,13 +50,17 @@
4650
import org.apache.hadoop.hbase.client.Scan;
4751
import org.apache.hadoop.hbase.client.Table;
4852
import org.apache.hadoop.hbase.util.Bytes;
53+
import org.apache.phoenix.jdbc.PhoenixResultSet;
4954
import org.apache.phoenix.query.PhoenixTestBuilder.BasicDataWriter;
5055
import org.apache.phoenix.query.PhoenixTestBuilder.DataSupplier;
5156
import org.apache.phoenix.query.PhoenixTestBuilder.DataWriter;
5257
import org.apache.phoenix.query.PhoenixTestBuilder.SchemaBuilder;
5358
import org.apache.phoenix.query.QueryConstants;
5459
import org.apache.phoenix.schema.PTable;
60+
import org.apache.phoenix.util.EnvironmentEdgeManager;
5561
import org.apache.phoenix.util.IndexUtil;
62+
import org.apache.phoenix.util.ManualEnvironmentEdge;
63+
import org.apache.phoenix.util.QueryUtil;
5664
import org.apache.phoenix.util.SchemaUtil;
5765
import org.apache.phoenix.util.TestUtil;
5866
import org.junit.Ignore;
@@ -703,6 +711,55 @@ public List<Object> getValues(int rowIndex) {
703711
}
704712
}
705713

714+
/**
715+
* Test that the empty column cell is returned by the scan when there is a DistinctPrefixFilter
716+
* and an EmptyColumnOnlyFilter. If there is no empty column cell returned in the scan then TTL
717+
* masking logic can break.
718+
*/
719+
@Test
720+
public void testMaskingWithDistinctPrefixFilter() throws Exception {
721+
try (Connection conn = DriverManager.getConnection(getUrl())) {
722+
int ttl = 10;
723+
String dataTableName = generateUniqueName();
724+
// not using column encoding so that we use EmptyColumnOnlyFilter
725+
String ddl = "create table " + dataTableName + " (id1 varchar(10) not null , "
726+
+ "id2 varchar(10) not null , val1 varchar(10), val2 varchar(10) "
727+
+ "constraint PK primary key (id1, id2)) COLUMN_ENCODED_BYTES=0, TTL=" + ttl;
728+
conn.createStatement().execute(ddl);
729+
730+
String[] expectedValues = { "val1_1", "val1_2", "val1_3", "val1_4" };
731+
String dml = "UPSERT INTO " + dataTableName + " VALUES(?, ?, ?, ?)";
732+
PreparedStatement ps = conn.prepareStatement(dml);
733+
for (int id1 = 0; id1 < 5; ++id1) {
734+
ps.setString(1, "id1_" + id1);
735+
for (int id2 = 0; id2 < 5; ++id2) {
736+
ps.setString(2, "id2_" + id2);
737+
ps.setString(3, "val1_" + id1 % 2);
738+
ps.setString(4, "val2_" + id2 % 2);
739+
ps.executeUpdate();
740+
}
741+
}
742+
conn.commit();
743+
try {
744+
ManualEnvironmentEdge injectEdge = new ManualEnvironmentEdge();
745+
// expire the rows
746+
injectEdge.setValue(EnvironmentEdgeManager.currentTimeMillis() + ttl * 1000 + 2);
747+
EnvironmentEdgeManager.injectEdge(injectEdge);
748+
String distinctQuery = "SELECT DISTINCT id1 FROM " + dataTableName;
749+
try (ResultSet rs = conn.createStatement().executeQuery(distinctQuery)) {
750+
PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class);
751+
String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator());
752+
assertTrue(explainPlan.contains("SERVER FILTER BY EMPTY COLUMN ONLY"));
753+
assertTrue(explainPlan.contains("SERVER DISTINCT PREFIX FILTER OVER"));
754+
// all the rows should have been masked
755+
assertFalse(rs.next());
756+
}
757+
} finally {
758+
EnvironmentEdgeManager.reset();
759+
}
760+
}
761+
}
762+
706763
private void upsertDataAndRunValidations(int numRowsToUpsert,
707764
ExpectedTestResults expectedTestResults, DataWriter dataWriter, SchemaBuilder schemaBuilder,
708765
List<Integer> overriddenColumnsPositions) throws Exception {

phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.sql.ResultSet;
4343
import java.sql.SQLException;
4444
import java.sql.Timestamp;
45+
import java.util.Arrays;
4546
import java.util.Calendar;
4647
import java.util.Collection;
4748
import java.util.List;
@@ -64,6 +65,7 @@
6465
import org.apache.phoenix.util.ReadOnlyProps;
6566
import org.apache.phoenix.util.TestUtil;
6667
import org.junit.After;
68+
import org.junit.Assume;
6769
import org.junit.Before;
6870
import org.junit.BeforeClass;
6971
import org.junit.Test;
@@ -1661,6 +1663,45 @@ public void testOnDuplicateKeyWithIndex() throws Exception {
16611663
}
16621664
}
16631665

1666+
@Test
1667+
public void testWithDistinctPrefixFilter() throws Exception {
1668+
Assume.assumeTrue(async == false);
1669+
try (Connection conn = DriverManager.getConnection(getUrl())) {
1670+
String dataTableName = generateUniqueName();
1671+
String indexTableName = generateUniqueName();
1672+
String ddl = "create table " + dataTableName + " (id varchar(10) not null primary key, "
1673+
+ "val1 varchar(10), val2 varchar(10), val3 varchar(10))" + tableDDLOptions;
1674+
conn.createStatement().execute(ddl);
1675+
ddl = "create index " + indexTableName + " on " + dataTableName
1676+
+ " (val1) include (val2, val3)" + this.indexDDLOptions;
1677+
conn.createStatement().execute(ddl);
1678+
String[] expectedValues = { "val1_1", "val1_2", "val1_3", "val1_4" };
1679+
int rowCount = 20;
1680+
String dml = "UPSERT INTO " + dataTableName + " VALUES(?, ?, ?, ?)";
1681+
PreparedStatement ps = conn.prepareStatement(dml);
1682+
for (int id = 0; id < rowCount; ++id) {
1683+
ps.setString(1, "id_" + id);
1684+
ps.setString(2, expectedValues[id % expectedValues.length]);
1685+
ps.setString(3, "val2_" + id % 2);
1686+
ps.setString(4, "val3");
1687+
ps.executeUpdate();
1688+
}
1689+
conn.commit();
1690+
String distinctQuery = "SELECT DISTINCT val1 FROM " + dataTableName;
1691+
try (ResultSet rs = conn.createStatement().executeQuery(distinctQuery)) {
1692+
PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class);
1693+
String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator());
1694+
assertTrue(explainPlan.contains(indexTableName));
1695+
assertTrue(explainPlan.contains("SERVER DISTINCT PREFIX FILTER OVER"));
1696+
List actualValues = Lists.newArrayList();
1697+
while (rs.next()) {
1698+
actualValues.add(rs.getString(1));
1699+
}
1700+
assertEquals(Arrays.asList(expectedValues), actualValues);
1701+
}
1702+
}
1703+
}
1704+
16641705
public static void commitWithException(Connection conn) {
16651706
try {
16661707
conn.commit();

0 commit comments

Comments
 (0)