From b024558c79c063a5956c0923f608fd6ee108949c Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Thu, 23 Oct 2025 12:18:49 +0200 Subject: [PATCH 1/4] ES|QL: manage INLINE STATS count(*) on result sets with no columns --- .../aggregation/CountAggregatorFunction.java | 35 +++++++++++++------ .../src/main/resources/inlinestats.csv-spec | 17 +++++++++ .../xpack/esql/action/EsqlCapabilities.java | 6 ++++ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/CountAggregatorFunction.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/CountAggregatorFunction.java index a9d21babfbd9c..491451c50a210 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/CountAggregatorFunction.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/CountAggregatorFunction.java @@ -78,23 +78,38 @@ public int intermediateBlockCount() { } private int blockIndex() { - return countAll ? 0 : channels.get(0); + return countAll ? -1 : channels.get(0); } @Override public void addRawInput(Page page, BooleanVector mask) { - Block block = page.getBlock(blockIndex()); - LongState state = this.state; - int count; - if (mask.isConstant()) { - if (mask.getBoolean(0) == false) { - return; + if (countAll) { + // this will work also when the page has no blocks + if (mask.isConstant() && mask.getBoolean(0)) { + state.longValue(state.longValue() + page.getPositionCount()); + } else { + int count = 0; + for (int i = 0; i < mask.getPositionCount(); i++) { + if (mask.getBoolean(i)) { + count++; + } + } + state.longValue(state.longValue() + count); } - count = countAll ? block.getPositionCount() : block.getTotalValueCount(); } else { - count = countMasked(block, mask); + Block block = page.getBlock(blockIndex()); + LongState state = this.state; + int count; + if (mask.isConstant()) { + if (mask.getBoolean(0) == false) { + return; + } + count = countAll ? block.getPositionCount() : block.getTotalValueCount(); + } else { + count = countMasked(block, mask); + } + state.longValue(state.longValue() + count); } - state.longValue(state.longValue() + count); } private int countMasked(Block block, BooleanVector mask) { diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec index e6755876dfda3..82def6f578996 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec @@ -4257,3 +4257,20 @@ from employees c:long 1 ; + + +inlineStatsKeepDropCountStar +required_capability: inline_stats_with_no_columns + +from employees +| keep emp_no +| drop emp_no +| inline stats c = count(*) +| limit 3 +; + +c:long +100 +100 +100 +; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index 84a5103200a14..2ae75637efdbd 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -1558,6 +1558,12 @@ public enum Cap { * Temporarily forbid the use of an explicit or implicit LIMIT before INLINE STATS. */ FORBID_LIMIT_BEFORE_INLINE_STATS(INLINE_STATS.enabled), + + /** + * https://github.com/elastic/elasticsearch/issues/136851 + */ + INLINE_STATS_WITH_NO_COLUMNS(INLINE_STATS.enabled), + // Last capability should still have a comma for fewer merge conflicts when adding new ones :) // This comment prevents the semicolon from being on the previous capability when Spotless formats the file. ; From 6526e5a89a198d661ee9c372d94fcdd9a29c7e22 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Thu, 23 Oct 2025 12:21:34 +0200 Subject: [PATCH 2/4] Update docs/changelog/137017.yaml --- docs/changelog/137017.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/137017.yaml diff --git a/docs/changelog/137017.yaml b/docs/changelog/137017.yaml new file mode 100644 index 0000000000000..538776a2504ee --- /dev/null +++ b/docs/changelog/137017.yaml @@ -0,0 +1,5 @@ +pr: 137017 +summary: Manage INLINE STATS count(*) on result sets with no columns +area: ES|QL +type: bug +issues: [] From 7e4a92db97ab86124c4ee901d8d15cc1079239bc Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Thu, 23 Oct 2025 12:29:04 +0200 Subject: [PATCH 3/4] Test with ROW --- .../src/main/resources/inlinestats.csv-spec | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec index 82def6f578996..03c2cc528be46 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec @@ -4274,3 +4274,15 @@ c:long 100 100 ; + +rowInlineStatsKeepDropCountStar +required_capability: inline_stats_with_no_columns + +row a = 1 +| drop a +| inline stats c = count(*) +; + +c:long +1 +; From 5345455d23cf81eb633758f6eb7177a0fc6e11c2 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Thu, 23 Oct 2025 15:27:43 +0200 Subject: [PATCH 4/4] Simplify + comments --- .../compute/aggregation/CountAggregatorFunction.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/CountAggregatorFunction.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/CountAggregatorFunction.java index 491451c50a210..a348d08138273 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/CountAggregatorFunction.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/CountAggregatorFunction.java @@ -78,6 +78,10 @@ public int intermediateBlockCount() { } private int blockIndex() { + // In case of countAll, block index is irrelevant. + // Page.positionCount should be used instead, + // because the page could have zero blocks + // (drop all columns scenario) return countAll ? -1 : channels.get(0); } @@ -104,7 +108,7 @@ public void addRawInput(Page page, BooleanVector mask) { if (mask.getBoolean(0) == false) { return; } - count = countAll ? block.getPositionCount() : block.getTotalValueCount(); + count = block.getTotalValueCount(); } else { count = countMasked(block, mask); }