Skip to content

Conversation

@lcawl
Copy link
Owner

@lcawl lcawl commented Jan 14, 2026

This PR is a test of the docs-builder changelog add and docs-builder changelog bundle commands.

In particular, it contains output files generated via elastic/docs-builder#2350 and elastic/docs-builder#2461

Steps

  1. Obtain a list of the Elasticsearch PRs that were included in the serverless release (in this case I've put them in a txt file for temporary re-use).
  2. Create the bundles from the PR list (since the format is different than the existing changelogs):
    docs-builder changelog add \
    --prs ./docs/elasticsearch-2025-01-13.txt \ 
    --repo elasticsearch --owner elastic --products "cloud-serverless 2025-01-13" \
    --config ./docs/changelog.yml \
    --output ./docs/changelog/2025-01-13 \
    --strip-title-prefix --use-pr-number
  3. Create a bundle of all these changelogs:
    docs-builder changelog bundle --all \
    --directory ./docs/changelog/2025-01-13 \
    --output ./docs/release-notes/changelog-bundles/cloud-serverless-2025-01-13.yaml \
    --output-products "cloud-serverless 2025-01-13 ga" \
    --resolve
    Alternatively, we could have used the --prs or --input-products options to filter the bundle per https://docs-v3-preview.elastic.dev/elastic/docs-builder/tree/main/contribute/changelog#changelog-bundle

lcawl pushed a commit that referenced this pull request Jan 23, 2026
…tic#140027)

This PR fixes the issue where `INLINE STATS GROUP BY null` was being
incorrectly pruned by `PruneLeftJoinOnNullMatchingField`.

Fixes elastic#139887

## Problem For query:

```
FROM employees
| INLINE STATS c = COUNT(*) BY n = null
| KEEP c, n
| LIMIT 3
```

During `LogicalPlanOptimizer`:

```
Limit[3[INTEGER],false,false]
\_EsqlProject[[c{r}#2, n{r}elastic#4]]
  \_InlineJoin[LEFT,[n{r}elastic#4],[n{r}elastic#4]]
    |_Eval[[null[NULL] AS n#4]]
    | \_EsRelation[employees][<no-fields>{r$}elastic#7]
    \_Aggregate[[n{r}elastic#4],[COUNT(*[KEYWORD],true[BOOLEAN],PT0S[TIME_DURATION]) AS c#2, n{r}elastic#4]]
      \_StubRelation[[<no-fields>{r$}elastic#7, n{r}elastic#4]]
```

The following join node:

```
InlineJoin[LEFT,[n{r}elastic#4],[n{r}elastic#4]]
|_Eval[[null[NULL] AS n#4]]
| \_EsRelation[employees][<no-fields>{r$}elastic#7]
\_Aggregate[[n{r}elastic#4],[COUNT(*[KEYWORD],true[BOOLEAN],PT0S[TIME_DURATION]) AS c#2, n{r}elastic#4]]
  \_StubRelation[[<no-fields>{r$}elastic#7, n{r}elastic#4]]
```

should NOT have `PruneLeftJoinOnNullMatchingField` applied, because the
right side is an `Aggregate` (originating from `INLINE STATS`). Since
`STATS` supports `GROUP BY null`, the join key being null is a valid use
case. Pruning this join would incorrectly eliminate the aggregation
results, changing the query semantics.

During `LocalLogicalPlanOptimizer`:

```
ProjectExec[[c{r}#2, n{r}elastic#4]]
\_LimitExec[3[INTEGER],null]
  \_ExchangeExec[[c{r}#2, n{r}elastic#4],false]
    \_FragmentExec[filter=null, estimatedRowSize=0, reducer=[], fragment=[<>
Project[[c{r}#2, n{r}elastic#4]]
\_Limit[3[INTEGER],false,false]
  \_InlineJoin[LEFT,[n{r}elastic#4],[n{r}elastic#4]]
    |_Eval[[null[NULL] AS n#4]]
    | \_EsRelation[employees][<no-fields>{r$}elastic#7]
    \_LocalRelation[[c{r}#2, n{r}elastic#4],Page{blocks=[LongVectorBlock[vector=ConstantLongVector[positions=1, value=100]], ConstantNullBlock[positions=1]]}]<>]]
```

The following join node:

```
InlineJoin[LEFT,[n{r}elastic#4],[n{r}elastic#4]]
|_Eval[[null[NULL] AS n#4]]
| \_EsRelation[employees][<no-fields>{r$}elastic#7]
\_LocalRelation[[c{r}#2, n{r}elastic#4],Page{blocks=[LongVectorBlock[vector=ConstantLongVector[positions=1, value=100]], ConstantNullBlock[positions=1]]}]
```

should NOT have `PruneLeftJoinOnNullMatchingField` applied, because the
right side is a `LocalRelation` (the `Aggregate` was optimized into a
`LocalRelation` containing the pre-computed aggregation results).
Pruning this join when the join key is null would discard the valid
aggregation results stored in the `LocalRelation`, incorrectly producing
null values instead of the expected count.

## Solution The fix ensures that `PruneLeftJoinOnNullMatchingField` only
applies to `LOOKUP JOIN` nodes, where `join.right()` is an `EsRelation`.
For `INLINE STATS` joins, the right side can be:

 - `Aggregate` (before optimization), or
 - `LocalRelation` (after the aggregate is optimized)

By checking `join.right() instanceof EsRelation`, we correctly skip the
pruning optimization for `INLINE STATS` joins, preserving the expected
query results when grouping by null.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants