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

loserwang1024 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git


The following commit(s) were added to refs/heads/main by this push:
     new 305a3a1bd [flink] Fix COUNT(column) aggregate pushdown to reject 
nullable columns (#3271)
305a3a1bd is described below

commit 305a3a1bda93060145e8307a3aa4d4096b448f9c
Author: Junbo Wang <[email protected]>
AuthorDate: Mon May 18 20:40:51 2026 +0800

    [flink] Fix COUNT(column) aggregate pushdown to reject nullable columns 
(#3271)
---
 .../fluss/flink/source/FlinkTableSource.java       | 37 ++++++++----
 .../flink/source/FlinkTableSourceBatchITCase.java  | 68 ++++++++++++++++++++--
 2 files changed, 87 insertions(+), 18 deletions(-)

diff --git 
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkTableSource.java
 
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkTableSource.java
index 859ef7cfa..b0bba81a7 100644
--- 
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkTableSource.java
+++ 
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkTableSource.java
@@ -78,7 +78,6 @@ import org.apache.flink.table.data.RowData;
 import org.apache.flink.table.expressions.AggregateExpression;
 import org.apache.flink.table.expressions.ResolvedExpression;
 import org.apache.flink.table.functions.AsyncLookupFunction;
-import org.apache.flink.table.functions.FunctionDefinition;
 import org.apache.flink.table.functions.LookupFunction;
 import org.apache.flink.table.types.DataType;
 import org.apache.flink.table.types.logical.LogicalType;
@@ -843,19 +842,33 @@ public class FlinkTableSource
             return false;
         }
 
-        FunctionDefinition functionDefinition = 
aggregateExpressions.get(0).getFunctionDefinition();
-        if (!(functionDefinition
-                        .getClass()
-                        .getCanonicalName()
-                        .equals(
-                                
"org.apache.flink.table.planner.functions.aggfunctions.CountAggFunction")
-                || functionDefinition
-                        .getClass()
-                        .getCanonicalName()
-                        .equals(
-                                
"org.apache.flink.table.planner.functions.aggfunctions.Count1AggFunction"))) {
+        AggregateExpression aggExpr = aggregateExpressions.get(0);
+        String functionName = 
aggExpr.getFunctionDefinition().getClass().getCanonicalName();
+
+        // Verify that the aggregate function is COUNT(*) or COUNT(1)
+        // CountAggFunction: COUNT(*) or COUNT(column)
+        // Count1AggFunction: COUNT(1) with constant argument
+        boolean isCountAgg =
+                
"org.apache.flink.table.planner.functions.aggfunctions.CountAggFunction"
+                        .equals(functionName);
+        boolean isCount1Agg =
+                
"org.apache.flink.table.planner.functions.aggfunctions.Count1AggFunction"
+                        .equals(functionName);
+        if (!isCountAgg && !isCount1Agg) {
             return false;
         }
+
+        // For COUNT(column), reject if column is nullable (cannot handle NULL 
filtering)
+        if (isCountAgg) {
+            List<org.apache.flink.table.expressions.Expression> args = 
aggExpr.getChildren();
+            if (!args.isEmpty() && args.get(0) instanceof ResolvedExpression) {
+                ResolvedExpression arg = (ResolvedExpression) args.get(0);
+                if (arg.getOutputDataType().getLogicalType().isNullable()) {
+                    return false;
+                }
+            }
+        }
+
         selectRowCount = true;
         this.producedDataType = dataType.getLogicalType();
         return true;
diff --git 
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/FlinkTableSourceBatchITCase.java
 
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/FlinkTableSourceBatchITCase.java
index 48ee39af1..51d05fa03 100644
--- 
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/FlinkTableSourceBatchITCase.java
+++ 
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/FlinkTableSourceBatchITCase.java
@@ -324,9 +324,9 @@ abstract class FlinkTableSourceBatchITCase extends 
FlinkTestBase {
         List<String> expected =
                 Arrays.asList(
                         "+I[1, address1, name1]",
-                        "+I[2, address2, name2]",
+                        "+I[2, null, name2]",
                         "+I[3, address3, name3]",
-                        "+I[4, address4, name4]",
+                        "+I[4, null, name4]",
                         "+I[5, address5, name5]");
         assertThat(collected).isSubsetOf(expected);
         assertThat(collected).hasSize(2);
@@ -404,6 +404,33 @@ abstract class FlinkTableSourceBatchITCase extends 
FlinkTestBase {
         List<String> expected = Collections.singletonList("+I[5]");
         assertThat(collected).isEqualTo(expected);
 
+        // test COUNT(column) pushdown on non-nullable column
+        query = String.format("SELECT COUNT(id) FROM %s", tableName);
+        assertThat(tEnv.explainSql(query))
+                .contains(
+                        "aggregates=[grouping=[], 
aggFunctions=[Count1AggFunction()]]]], fields=[count1$0]");
+        iterRows = tEnv.executeSql(query).collect();
+        collected = collectRowsWithTimeout(iterRows, 1);
+        assertThat(collected).isEqualTo(expected);
+
+        // test COUNT(column) on nullable column - should NOT push down
+        // For PK table, this will fail because it doesn't support full scan 
in batch mode
+        assertThatThrownBy(
+                        () ->
+                                tEnv.explainSql(
+                                        String.format("SELECT COUNT(address) 
FROM %s", tableName)))
+                .hasMessageContaining(
+                        "Currently, Fluss only support queries on table with 
datalake enabled or point queries on primary key when it's in batch execution 
mode.");
+
+        assertThatThrownBy(
+                        () ->
+                                tEnv.explainSql(
+                                        String.format(
+                                                "SELECT COUNT(DISTINCT 
address) FROM %s",
+                                                tableName)))
+                .hasMessageContaining(
+                        "Currently, Fluss only support queries on table with 
datalake enabled or point queries on primary key when it's in batch execution 
mode.");
+
         // test not push down grouping count.
         assertThatThrownBy(
                         () ->
@@ -452,6 +479,32 @@ abstract class FlinkTableSourceBatchITCase extends 
FlinkTestBase {
         List<String> expected = 
Collections.singletonList(String.format("+I[%s]", expectedRows));
         assertThat(collected).isEqualTo(expected);
 
+        // test COUNT(column) pushdown
+        query = String.format("SELECT COUNT(id) FROM %s", tableName);
+        assertThat(tEnv.explainSql(query))
+                .contains(
+                        "aggregates=[grouping=[], 
aggFunctions=[Count1AggFunction()]]]], fields=[count1$0]");
+        iterRows = tEnv.executeSql(query).collect();
+        collected = collectRowsWithTimeout(iterRows, 1);
+        assertThat(collected).isEqualTo(expected);
+
+        // test COUNT(column) with NULL values - should NOT push down for 
nullable columns
+        // This will fail because log table doesn't support full scan in batch 
mode
+        assertThatThrownBy(
+                        () ->
+                                tEnv.explainSql(
+                                        String.format("SELECT COUNT(address) 
FROM %s", tableName)))
+                .hasMessageContaining(
+                        "Currently, Fluss only support queries on table with 
datalake enabled or point queries on primary key when it's in batch execution 
mode.");
+        assertThatThrownBy(
+                        () ->
+                                tEnv.explainSql(
+                                        String.format(
+                                                "SELECT COUNT(DISTINCT 
address) FROM %s",
+                                                tableName)))
+                .hasMessageContaining(
+                        "Currently, Fluss only support queries on table with 
datalake enabled or point queries on primary key when it's in batch execution 
mode.");
+
         // test not push down grouping count.
         assertThatThrownBy(
                         () ->
@@ -536,11 +589,11 @@ abstract class FlinkTableSourceBatchITCase extends 
FlinkTestBase {
 
         TablePath tablePath = TablePath.of(DEFAULT_DB, tableName);
 
-        // prepare table data
+        // prepare table data with NULL values in address column
         try (Table table = conn.getTable(tablePath)) {
             AppendWriter appendWriter = table.newAppend().createWriter();
             for (int i = 1; i <= 5; i++) {
-                Object[] values = new Object[] {i, "address" + i, "name" + i};
+                Object[] values = new Object[] {i, i % 2 == 0 ? null : 
"address" + i, "name" + i};
                 appendWriter.append(row(values));
                 // make sure every bucket has records
                 appendWriter.flush();
@@ -571,12 +624,15 @@ abstract class FlinkTableSourceBatchITCase extends 
FlinkTestBase {
                 
waitUntilPartitions(FLUSS_CLUSTER_EXTENSION.getZooKeeperClient(), tablePath);
         Collection<String> partitions = partitionNameById.values();
 
-        // prepare table data
+        // prepare table data with NULL values in address column
         try (Table table = conn.getTable(tablePath)) {
             AppendWriter appendWriter = table.newAppend().createWriter();
             for (int i = 1; i <= 5; i++) {
                 for (String partition : partitions) {
-                    Object[] values = new Object[] {i, "address" + i, "name" + 
i, partition};
+                    Object[] values =
+                            new Object[] {
+                                i, i % 2 == 0 ? null : "address" + i, "name" + 
i, partition
+                            };
                     appendWriter.append(row(values));
                     // make sure every bucket has records
                     appendWriter.flush();

Reply via email to