This is an automated email from the ASF dual-hosted git repository.

vjasani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/master by this push:
     new 3a9b391eea PHOENIX-7664 Remove EmptyColumnOnlyFilter and 
FirstKeyOnlyFilter for CDC scanners (#2221)
3a9b391eea is described below

commit 3a9b391eea0a467200436703365283c8dbc8de3b
Author: Viraj Jasani <[email protected]>
AuthorDate: Mon Jul 14 10:19:43 2025 -0700

    PHOENIX-7664 Remove EmptyColumnOnlyFilter and FirstKeyOnlyFilter for CDC 
scanners (#2221)
---
 .../java/org/apache/phoenix/util/ScanUtil.java     |  47 ++++++--
 .../coprocessor/BaseScannerRegionObserver.java     |   2 +-
 .../java/org/apache/phoenix/end2end/Bson3IT.java   |  76 ++++++++++--
 .../phoenix/schema/ConditionalTTLExpressionIT.java | 134 +++++++++++++--------
 4 files changed, 186 insertions(+), 73 deletions(-)

diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/util/ScanUtil.java 
b/phoenix-core-client/src/main/java/org/apache/phoenix/util/ScanUtil.java
index 86710eb51b..33724affec 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/ScanUtil.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/ScanUtil.java
@@ -25,13 +25,10 @@ import static 
org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverCons
 import static 
org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants.SCAN_STOP_ROW_SUFFIX;
 import static 
org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants.CDC_DATA_TABLE_DEF;
 import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.DEFAULT_TTL;
-import static 
org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TTL_DEFINED_IN_TABLE_DESCRIPTOR;
 import static 
org.apache.phoenix.query.QueryConstants.ENCODED_EMPTY_COLUMN_NAME;
 import static 
org.apache.phoenix.query.QueryServices.USE_STATS_FOR_PARALLELIZATION;
-import static 
org.apache.phoenix.query.QueryServicesOptions.DEFAULT_PHOENIX_TABLE_TTL_ENABLED;
 import static 
org.apache.phoenix.query.QueryServicesOptions.DEFAULT_USE_STATS_FOR_PARALLELIZATION;
 import static 
org.apache.phoenix.schema.LiteralTTLExpression.TTL_EXPRESSION_DEFINED_IN_TABLE_DESCRIPTOR;
-import static 
org.apache.phoenix.schema.LiteralTTLExpression.TTL_EXPRESSION_FOREVER;
 import static org.apache.phoenix.schema.types.PDataType.TRUE_BYTES;
 import static org.apache.phoenix.util.ByteUtil.EMPTY_BYTE_ARRAY;
 
@@ -44,7 +41,6 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
-import java.util.Set;
 import java.util.TreeMap;
 
 import org.apache.hadoop.conf.Configuration;
@@ -85,7 +81,6 @@ import 
org.apache.phoenix.filter.EncodedQualifiersColumnProjectionFilter;
 import org.apache.phoenix.filter.MultiEncodedCQKeyValueComparisonFilter;
 import org.apache.phoenix.filter.PagingFilter;
 import org.apache.phoenix.filter.SkipScanFilter;
-import org.apache.phoenix.hbase.index.covered.update.ColumnReference;
 import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
 import org.apache.phoenix.hbase.index.util.VersionUtil;
 import org.apache.phoenix.index.CDCTableInfo;
@@ -97,9 +92,7 @@ import org.apache.phoenix.query.KeyRange.Bound;
 import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.query.QueryServicesOptions;
-import org.apache.phoenix.schema.CompiledConditionalTTLExpression;
 import org.apache.phoenix.schema.CompiledTTLExpression;
-import org.apache.phoenix.schema.ConditionalTTLExpression;
 import org.apache.phoenix.schema.IllegalDataException;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PName;
@@ -110,7 +103,6 @@ import org.apache.phoenix.schema.PTableKey;
 import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.schema.RowKeySchema;
 import org.apache.phoenix.schema.SortOrder;
-import org.apache.phoenix.schema.TTLExpression;
 import org.apache.phoenix.schema.LiteralTTLExpression;
 import org.apache.phoenix.schema.TTLExpressionFactory;
 import org.apache.phoenix.schema.TableNotFoundException;
@@ -659,6 +651,45 @@ public class ScanUtil {
         return false;
     }
 
+    /**
+     * Adjusts the scan filter for CDC scans.
+     * Remove EmptyColumnOnlyFilter and FirstKeyOnlyFilter if present.
+     *
+     * @param scan the scan to adjust.
+     */
+    public static void adjustScanFilterForCDC(Scan scan) {
+        Filter originalFilter = scan.getFilter();
+
+        if (originalFilter instanceof FilterList) {
+            FilterList filterList = (FilterList) originalFilter;
+            List<Filter> filters = filterList.getFilters();
+            List<Filter> newFilters = new ArrayList<>();
+
+            boolean filtersRemoved = false;
+            for (Filter filter : filters) {
+                if (!(filter instanceof EmptyColumnOnlyFilter)
+                        && !(filter instanceof FirstKeyOnlyFilter)) {
+                    newFilters.add(filter);
+                } else {
+                    filtersRemoved = true;
+                }
+            }
+
+            if (filtersRemoved) {
+                if (newFilters.isEmpty()) {
+                    scan.setFilter(null);
+                } else if (newFilters.size() == 1) {
+                    scan.setFilter(newFilters.get(0));
+                } else {
+                    FilterList newFilterList = new 
FilterList(filterList.getOperator(), newFilters);
+                    scan.setFilter(newFilterList);
+                }
+            }
+        } else {
+            adjustScanFilterForGlobalIndexRegionScanner(scan);
+        }
+    }
+
     public static interface BytesComparator {
         public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int 
l2);
     };
diff --git 
a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/BaseScannerRegionObserver.java
 
b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/BaseScannerRegionObserver.java
index bb46a10cb2..b9eae0ecfb 100644
--- 
a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/BaseScannerRegionObserver.java
+++ 
b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/BaseScannerRegionObserver.java
@@ -160,7 +160,7 @@ abstract public class BaseScannerRegionObserver implements 
RegionObserver {
             byte[] cdcScan = scan.getAttribute(CDC_DATA_TABLE_DEF);
             if (cdcScan != null) {
                 CDCUtil.setupScanForCDC(scan);
-                ScanUtil.adjustScanFilterForGlobalIndexRegionScanner(scan);
+                ScanUtil.adjustScanFilterForCDC(scan);
             }
         }
         if (isRegionObserverFor(scan)) {
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson3IT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson3IT.java
index fb04899205..841eebb913 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson3IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson3IT.java
@@ -2191,19 +2191,51 @@ public class Bson3IT extends ParallelStatsDisabledIT {
       }
 
       // Verify TTL delete events for all rows
-      assertEquals("Should have TTL delete events for all 3 rows", 3, 
ttlDeleteEvents.size());
-
-      // Verify pre-image consistency for each row
-      for (String pk : Arrays.asList("pk0001", "pk1010", "pk1011")) {
-        Map<String, Object> ttlPreImage = ttlDeleteEvents.get(pk);
-        assertNotNull("Should have TTL delete event for " + pk, ttlPreImage);
-        Map<String, Object> lastPostImage = lastPostImages.get(pk);
-        assertNotNull("TTL pre-image should not be null for " + pk, 
ttlPreImage);
-        assertNotNull("Last post-image should not be null for " + pk, 
lastPostImage);
-        assertEquals("TTL delete pre-image should match last post-image for " 
+ pk,
-                lastPostImage, ttlPreImage);
+      verifyFinalPreImagesWithLastPostImages(ttlDeleteEvents, lastPostImages);
+
+      ttlDeleteQuery = "SELECT /*+ CDC_INCLUDE(PRE, POST) */ * FROM " + 
cdcName +
+              " WHERE PHOENIX_ROW_TIMESTAMP() > ? LIMIT 1";
+      ttlDeleteEvents = new HashMap<>();
+
+      try (PreparedStatement pst = conn.prepareStatement(ttlDeleteQuery)) {
+        pst.setTimestamp(1, beforeTTLTimestamp);
+        try (ResultSet ttlRs = pst.executeQuery()) {
+          while (ttlRs.next()) {
+            String pk = ttlRs.getString(2);
+            String cdcVal = ttlRs.getString(3);
+            Map<String, Object> cdcEvent = OBJECT_MAPPER.readValue(cdcVal, 
HashMap.class);
+
+            // Only process TTL delete events
+            if 
(CDC_TTL_DELETE_EVENT_TYPE.equals(cdcEvent.get(CDC_EVENT_TYPE))) {
+              ttlDeleteEvents.put(pk, (Map<String, Object>) 
cdcEvent.get(CDC_PRE_IMAGE));
+            }
+          }
+        }
+      }
+
+      assertEquals("Should have TTL delete events for initial 1 row", 1, 
ttlDeleteEvents.size());
+
+      ttlDeleteQuery = "SELECT /*+ CDC_INCLUDE(PRE, POST) */ * FROM " + 
cdcName +
+              " WHERE PHOENIX_ROW_TIMESTAMP() > ? LIMIT 3 OFFSET 1";
+
+      try (PreparedStatement pst = conn.prepareStatement(ttlDeleteQuery)) {
+        pst.setTimestamp(1, beforeTTLTimestamp);
+        try (ResultSet ttlRs = pst.executeQuery()) {
+          while (ttlRs.next()) {
+            String pk = ttlRs.getString(2);
+            String cdcVal = ttlRs.getString(3);
+            Map<String, Object> cdcEvent = OBJECT_MAPPER.readValue(cdcVal, 
HashMap.class);
+
+            // Only process TTL delete events
+            if 
(CDC_TTL_DELETE_EVENT_TYPE.equals(cdcEvent.get(CDC_EVENT_TYPE))) {
+              ttlDeleteEvents.put(pk, (Map<String, Object>) 
cdcEvent.get(CDC_PRE_IMAGE));
+            }
+          }
+        }
       }
 
+      verifyFinalPreImagesWithLastPostImages(ttlDeleteEvents, lastPostImages);
+
       ttlDeleteQuery = "SELECT /*+ CDC_INCLUDE(POST) */ * FROM " + cdcName +
               " WHERE PHOENIX_ROW_TIMESTAMP() > ?";
 
@@ -2220,11 +2252,29 @@ public class Bson3IT extends ParallelStatsDisabledIT {
           }
         }
       }
-      assertEquals("Total num of TTL_DELETE events without pre-image should be 
3 but it is " +
-                      nonePreImages, 3, nonePreImages);
+      assertEquals("Total num of TTL_DELETE events without pre-image should be 
3 but it is "
+              + nonePreImages, 3, nonePreImages);
     } finally {
       EnvironmentEdgeManager.reset();
     }
   }
 
+  private static void verifyFinalPreImagesWithLastPostImages(
+          Map<String, Map<String, Object>> ttlDeleteEvents,
+          Map<String, Map<String, Object>> lastPostImages) {
+    // Verify TTL delete events for all rows
+    assertEquals("Should have TTL delete events for all 3 rows", 3, 
ttlDeleteEvents.size());
+
+    // Verify pre-image consistency for each row
+    for (String pk : Arrays.asList("pk0001", "pk1010", "pk1011")) {
+      Map<String, Object> ttlPreImage = ttlDeleteEvents.get(pk);
+      assertNotNull("Should have TTL delete event for " + pk, ttlPreImage);
+      Map<String, Object> lastPostImage = lastPostImages.get(pk);
+      assertNotNull("TTL pre-image should not be null for " + pk, ttlPreImage);
+      assertNotNull("Last post-image should not be null for " + pk, 
lastPostImage);
+      assertEquals("TTL delete pre-image should match last post-image for " + 
pk,
+              lastPostImage, ttlPreImage);
+    }
+  }
+
 }
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/schema/ConditionalTTLExpressionIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/schema/ConditionalTTLExpressionIT.java
index 6815d52ebf..74c02f58d6 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/schema/ConditionalTTLExpressionIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/schema/ConditionalTTLExpressionIT.java
@@ -55,6 +55,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.util.Bytes;
@@ -450,31 +451,20 @@ public class ConditionalTTLExpressionIT extends 
ParallelStatsDisabledIT {
             assertEquals("Table should contain all inserted rows", 5, actual);
 
             // Query initial CDC events (inserts)
-            String cdcQuery = "SELECT /*+ CDC_INCLUDE(PRE, POST) */ * FROM " +
-                    PhoenixTestBuilder.DDLDefaults.DEFAULT_SCHEMA_NAME + "." + 
cdcName;
+            String cdcQuery1 = "SELECT /*+ CDC_INCLUDE(PRE, POST) */ * FROM "
+                    + PhoenixTestBuilder.DDLDefaults.DEFAULT_SCHEMA_NAME + "." 
+ cdcName;
 
-            ResultSet resultSet = 
conn.createStatement().executeQuery(cdcQuery);
-            List<Map<String, Object>> postImageList = new ArrayList<>();
-            while (resultSet.next()) {
-                String cdcVal = resultSet.getString(4);
-                Map<String, Object> map = OBJECT_MAPPER.readValue(cdcVal, 
Map.class);
+            String cdcQuery2 = "SELECT /*+ CDC_INCLUDE(PRE, POST) */ * FROM "
+                    + PhoenixTestBuilder.DDLDefaults.DEFAULT_SCHEMA_NAME + "." 
+ cdcName
+                    + " LIMIT 7";
 
-                // Validate insert events have no pre-image but have post-image
-                Map<String, Object> preImage =
-                        (Map<String, Object>) 
map.get(QueryConstants.CDC_PRE_IMAGE);
-                assertTrue("Insert events should have empty pre-image", 
preImage.isEmpty());
-
-                Map<String, Object> postImage =
-                        (Map<String, Object>) 
map.get(QueryConstants.CDC_POST_IMAGE);
-                assertFalse("Insert events should have non-empty post-image", 
postImage.isEmpty());
-                postImageList.add(postImage);
+            String cdcQuery3 = "SELECT /*+ CDC_INCLUDE(PRE, POST) */ * FROM "
+                    + PhoenixTestBuilder.DDLDefaults.DEFAULT_SCHEMA_NAME + "." 
+ cdcName
+                    + " LIMIT 10 OFFSET 2";
 
-                assertEquals("Initial events should be UPSERT type",
-                        QueryConstants.CDC_UPSERT_EVENT_TYPE,
-                        map.get(QueryConstants.CDC_EVENT_TYPE));
-            }
-            assertEquals("Post image list size should be 5 but it is " + 
postImageList.size(), 5,
-                    postImageList.size());
+            List<Map<String, Object>> postImageList1 = getPostImageList(conn, 
cdcQuery1, 5);
+            List<Map<String, Object>> postImageList2 = getPostImageList(conn, 
cdcQuery2, 5);
+            List<Map<String, Object>> postImageList3 = getPostImageList(conn, 
cdcQuery3, 3);
 
             // TTL Expiration - Advance time to trigger TTL expiration
             injectEdge.incrementValue(ttl);
@@ -484,32 +474,13 @@ public class ConditionalTTLExpressionIT extends 
ParallelStatsDisabledIT {
             actual = TestUtil.getRowCount(conn, tableName, true);
             assertEquals("All rows should be expired after TTL", 0, actual);
 
+            cdcQuery2 = "SELECT /*+ CDC_INCLUDE(PRE, POST) */ * FROM "
+                    + PhoenixTestBuilder.DDLDefaults.DEFAULT_SCHEMA_NAME + "." 
+ cdcName
+                    + " LIMIT 11";
             // TTL CDC Events - Validate TTL_DELETE events are generated
-            resultSet = conn.createStatement().executeQuery(cdcQuery);
-            int i = 0;
-            while (resultSet.next()) {
-                String cdcVal = resultSet.getString(4);
-                Map<String, Object> map = OBJECT_MAPPER.readValue(cdcVal, 
Map.class);
-
-                // Validate TTL delete events
-                assertEquals("TTL expired rows should generate TTL_DELETE 
events",
-                        QueryConstants.CDC_TTL_DELETE_EVENT_TYPE,
-                        map.get(QueryConstants.CDC_EVENT_TYPE));
-
-                Map<String, Object> preImage =
-                        (Map<String, Object>) 
map.get(QueryConstants.CDC_PRE_IMAGE);
-                assertFalse("TTL_DELETE events should have non-empty 
pre-image",
-                        preImage.isEmpty());
-
-                assertNull("TTL_DELETE events should have empty post-image",
-                        map.get(QueryConstants.CDC_POST_IMAGE));
-
-                // TTL delete pre-image should match previous upsert post-image
-                assertEquals("TTL_DELETE pre-image should match original 
insert post-image",
-                        postImageList.get(i), preImage);
-                i++;
-            }
-            assertEquals("Num of TTL_DELETE events verified should be 5 but it 
is " + i, 5, i);
+            compareTtlPreImagesWithLastPostImages(conn, cdcQuery1, 
postImageList1, 5);
+            compareTtlPreImagesWithLastPostImages(conn, cdcQuery2, 
postImageList2, 5);
+            compareTtlPreImagesWithLastPostImages(conn, cdcQuery3, 
postImageList3, 3);
 
             // Update an expired row to bring it back
             injectEdge.incrementValue(1);
@@ -542,13 +513,13 @@ public class ConditionalTTLExpressionIT extends 
ParallelStatsDisabledIT {
             validateTable(conn, tableName, expectedCellCount, 
dataRowPosToKey.values());
 
             // Query CDC events
-            cdcQuery = "SELECT /*+ CDC_INCLUDE(PRE, POST) */ * FROM " +
+            String cdcQuery = "SELECT /*+ CDC_INCLUDE(PRE, POST) */ * FROM " +
                     PhoenixTestBuilder.DDLDefaults.DEFAULT_SCHEMA_NAME + "." + 
cdcName
                     + " WHERE PHOENIX_ROW_TIMESTAMP() >= ?";
             PreparedStatement ps = conn.prepareStatement(cdcQuery);
             ps.setTimestamp(1, new Timestamp(currentTime));
-            resultSet = ps.executeQuery();
-            postImageList = new ArrayList<>();
+            ResultSet resultSet = ps.executeQuery();
+            List<Map<String, Object>> postImageList = new ArrayList<>();
             while (resultSet.next()) {
                 String cdcVal = resultSet.getString(4);
                 Map<String, Object> map = OBJECT_MAPPER.readValue(cdcVal, 
Map.class);
@@ -585,7 +556,7 @@ public class ConditionalTTLExpressionIT extends 
ParallelStatsDisabledIT {
             ps = conn.prepareStatement(cdcQuery);
             ps.setTimestamp(1, new Timestamp(currentTime));
             resultSet = ps.executeQuery();
-            i = 0;
+            int i = 0;
             while (resultSet.next()) {
                 String cdcVal = resultSet.getString(4);
                 Map<String, Object> map = OBJECT_MAPPER.readValue(cdcVal, 
Map.class);
@@ -610,6 +581,67 @@ public class ConditionalTTLExpressionIT extends 
ParallelStatsDisabledIT {
         }
     }
 
+    private static void compareTtlPreImagesWithLastPostImages(Connection conn, 
String cdcQuery,
+                                                              List<Map<String, 
Object>> postImageList,
+                                                              int 
expectedCount)
+            throws SQLException, JsonProcessingException {
+        ResultSet resultSet = conn.createStatement().executeQuery(cdcQuery);
+        int i = 0;
+        while (resultSet.next()) {
+            String cdcVal = resultSet.getString(4);
+            Map<String, Object> map = OBJECT_MAPPER.readValue(cdcVal, 
Map.class);
+
+            // Validate TTL delete events
+            assertEquals("TTL expired rows should generate TTL_DELETE events",
+                    QueryConstants.CDC_TTL_DELETE_EVENT_TYPE,
+                    map.get(QueryConstants.CDC_EVENT_TYPE));
+
+            Map<String, Object> preImage =
+                    (Map<String, Object>) 
map.get(QueryConstants.CDC_PRE_IMAGE);
+            assertFalse("TTL_DELETE events should have non-empty pre-image",
+                    preImage.isEmpty());
+
+            assertNull("TTL_DELETE events should have empty post-image",
+                    map.get(QueryConstants.CDC_POST_IMAGE));
+
+            // TTL delete pre-image should match previous upsert post-image
+            assertEquals("TTL_DELETE pre-image should match original insert 
post-image",
+                    postImageList.get(i), preImage);
+            i++;
+        }
+        assertEquals(
+                "Num of TTL_DELETE events verified should be " + expectedCount 
+ " but it is " + i,
+                expectedCount, i);
+    }
+
+    private static List<Map<String, Object>> getPostImageList(Connection conn, 
String cdcQuery,
+                                                              int 
expectedCount)
+            throws SQLException, JsonProcessingException {
+        ResultSet resultSet = conn.createStatement().executeQuery(cdcQuery);
+        List<Map<String, Object>> postImageList = new ArrayList<>();
+        while (resultSet.next()) {
+            String cdcVal = resultSet.getString(4);
+            Map<String, Object> map = OBJECT_MAPPER.readValue(cdcVal, 
Map.class);
+
+            // Validate insert events have no pre-image but have post-image
+            Map<String, Object> preImage =
+                    (Map<String, Object>) 
map.get(QueryConstants.CDC_PRE_IMAGE);
+            assertTrue("Insert events should have empty pre-image", 
preImage.isEmpty());
+
+            Map<String, Object> postImage =
+                    (Map<String, Object>) 
map.get(QueryConstants.CDC_POST_IMAGE);
+            assertFalse("Insert events should have non-empty post-image", 
postImage.isEmpty());
+            postImageList.add(postImage);
+
+            assertEquals("Initial events should be UPSERT type",
+                    QueryConstants.CDC_UPSERT_EVENT_TYPE,
+                    map.get(QueryConstants.CDC_EVENT_TYPE));
+        }
+        assertEquals("Post image list size should be " + expectedCount + " but 
it is "
+                + postImageList.size(), expectedCount, postImageList.size());
+        return postImageList;
+    }
+
     @Test
     public void testDeleteMarkers() throws Exception {
         String ttlCol = "VAL5";

Reply via email to