Skip to content

Commit f61f14d

Browse files
authored
[BugFix] Fix bin/chart NPE with null values (#5174) (#5334)
* [BugFix] Fix bin/chart NPE with null values (#5174) Bin bucket functions declared non-nullable VARCHAR return type, causing Calcite to optimize away IS NOT NULL filters and allowing null group keys to reach TreeMap which crashes in compareTo. Fix: declare return type as nullable using FORCE_NULLABLE and add nullsLast to chart command sorts as a defensive measure. Signed-off-by: Heng Qian <qianheng@amazon.com> * Remove stale /dedupe section from CLAUDE_GUIDE.md The dedupe command and scripts/comment-on-duplicates.sh were removed from main, but CLAUDE_GUIDE.md still referenced them, causing linkchecker CI failure. Signed-off-by: Heng Qian <qianheng@amazon.com> * Fix incorrect YAML test assertion and add datarows validation - Fix total count from 3 to 2 in YAML REST test (bin+chart with category produces 2 groups, not 3) - Add datarows assertions to both YAML test cases for row-level validation - Correct table statistics row count in unit test Signed-off-by: Heng Qian <qianheng@amazon.com> * Update ppl-bugfix-reference: prefer datarows match over length check YAML REST tests should assert actual row content, not just count. Learned from #5174 where total-only assertion missed incorrect values. Signed-off-by: Heng Qian <qianheng@amazon.com> * Improve harness: worktree tracking, completion gate, pwd reporting - Add pwd/branch reporting at harness start so caller knows the working directory - Add Completion Gate with git status check to prevent uncommitted changes from being left behind - Skill Step 2B reuses existing worktree if PR branch is already checked out, instead of always creating a new one - Step 3 requires main agent to record worktree→PR mapping for directing future operations to the correct directory Signed-off-by: Heng Qian <qianheng@amazon.com> * Add CalciteBinChartNullIT to CalciteNoPushdownIT suite bin+chart with null values should also be tested with pushdown disabled. Signed-off-by: Heng Qian <qianheng@amazon.com> * Prefer adding tests to existing IT suites over creating new ones Note in harness Phase 2 that new IT classes should be added to CalciteNoPushdownIT suite. Signed-off-by: Heng Qian <qianheng@amazon.com> --------- Signed-off-by: Heng Qian <qianheng@amazon.com>
1 parent 582b4d2 commit f61f14d

13 files changed

Lines changed: 410 additions & 30 deletions

File tree

.claude/commands/ppl-bugfix.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,33 @@ Agent(
8383

8484
### 2B: Follow-up
8585

86+
Before dispatching, check if an existing worktree already has the PR branch checked out:
87+
88+
```bash
89+
# List worktrees and find one on the PR branch
90+
for wt in .claude/worktrees/agent-*/; do
91+
branch=$(git -C "$wt" branch --show-current 2>/dev/null)
92+
if [ "$branch" = "<pr_branch>" ]; then
93+
echo "REUSE: $wt (branch: $branch)"
94+
fi
95+
done
96+
```
97+
98+
**If existing worktree found**: Do NOT use `isolation: "worktree"`. Pass the worktree path in the prompt so the subagent works there directly.
99+
100+
```
101+
Agent(
102+
mode: "<resolved_mode>",
103+
name: "bugfix-<issue_number>",
104+
description: "PPL bugfix #<issue_number> followup",
105+
prompt: "cd <worktree_path> first, then read .claude/harness/ppl-bugfix-followup.md and follow it.
106+
PR: <pr_number> (<pr_url>), Issue: #<issue_number>
107+
Working directory: <worktree_path>"
108+
)
109+
```
110+
111+
**If no existing worktree**: Create a new one.
112+
86113
```
87114
Agent(
88115
mode: "<resolved_mode>",
@@ -100,6 +127,16 @@ After all subagents complete, report a summary for each:
100127
- Classification, fix summary, PR URL, worktree path and branch, items needing human attention (2A)
101128
- What was addressed, current PR state, whether another round is needed (2B)
102129

130+
**Always include the worktree→PR mapping** from the subagent's output, e.g.:
131+
132+
```
133+
Worktree: /path/to/.claude/worktrees/agent-xxxx
134+
Branch: bugfix-1234
135+
PR: #5678
136+
```
137+
138+
**Important**: After reporting, the main agent must remember this mapping. When the user later asks to make changes to the PR (e.g., "commit this to PR #5678"), operate in the worktree directory — not the main session directory.
139+
103140
## Subagent Lifecycle
104141

105142
Subagents are task-scoped. They complete and release context — they cannot poll for events.

.claude/harness/ppl-bugfix-followup.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@
66

77
---
88

9+
## Report Working Directory
10+
11+
```bash
12+
echo "Worktree: $(pwd)"
13+
echo "Branch: $(git branch --show-current)"
14+
```
15+
16+
Include this in your output so the caller knows where changes are happening.
17+
918
## Reconstruct Context
1019

11-
The follow-up agent runs in a fresh worktree. First checkout the PR branch, then load state:
20+
First checkout the PR branch, then load state:
1221

1322
```bash
1423
# Checkout the PR branch in this worktree
@@ -101,3 +110,16 @@ For each comment addressed (bot or human):
101110
- **Did the follow-up workflow itself miss this signal?** → Update this file
102111

103112
If any improvement is needed, make the edit and include it in the same commit.
113+
114+
## Completion Gate
115+
116+
Before reporting "done":
117+
118+
1. Run `git status --porcelain` — if any uncommitted changes remain, commit and push them. This includes harness edits from Retrospective.
119+
2. Report in your final output:
120+
121+
```
122+
Worktree: <absolute path>
123+
Branch: <branch name>
124+
PR: <pr_number>
125+
```

.claude/harness/ppl-bugfix-harness.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## Phase 0: Triage
44

5+
### 0.0 Report Working Directory
6+
7+
```bash
8+
echo "Worktree: $(pwd)"
9+
echo "Branch: $(git branch --show-current)"
10+
```
11+
12+
Include this in your output so the caller knows where changes are happening.
13+
514
### 0.1 Load & Reproduce
615

716
```bash
@@ -53,7 +62,7 @@ Consult `.claude/harness/ppl-bugfix-reference.md` for test templates.
5362
Required deliverables:
5463
- Failing test reproducing the bug (written BEFORE the fix)
5564
- Unit tests covering happy path and edge cases
56-
- Integration test (`*IT.java` extending `CalcitePPLIT`)
65+
- Integration test — add to an existing `*IT.java` when possible; if creating a new one, add it to `CalciteNoPushdownIT`
5766
- YAML REST test at `integ-test/src/yamlRestTest/resources/rest-api-spec/test/issues/<ISSUE>.yml`
5867

5968
---
@@ -126,6 +135,8 @@ EOF
126135

127136
## Completion Gate
128137

138+
Run `git status --porcelain` — if any uncommitted changes remain, commit and push them before proceeding.
139+
129140
Do NOT report "done" until every item below is checked. List each in your final report:
130141

131142
- [ ] **Unit tests**: New test class or methods
@@ -149,3 +160,10 @@ If any item is blocked, report which and why.
149160
- [ ] New pattern? Add to Case Index.
150161

151162
Include harness improvements in the same PR.
163+
164+
Report in your final output:
165+
```
166+
Worktree: <absolute path>
167+
Branch: <branch name>
168+
PR: <pr_number>
169+
```

.claude/harness/ppl-bugfix-reference.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,13 @@ teardown:
109109
headers: { Content-Type: 'application/json' }
110110
ppl: { body: { query: "source=test_issue_<ISSUE> | <your PPL>" } }
111111
- match: { total: <expected> }
112-
- length: { datarows: <expected> }
112+
- match: { datarows: [ [ <row1_val1>, <row1_val2> ], [ <row2_val1>, <row2_val2> ] ] }
113113
```
114114
115+
> **Always include `datarows` assertions** — verifying only `total` and `schema` will miss
116+
> wrong values. Count the expected output groups carefully (e.g., for `chart ... by <col>`,
117+
> count distinct (row_split, col_split) groups after null filtering, not the number of input rows).
118+
115119
---
116120

117121
## Symptom → Fix Path

CLAUDE_GUIDE.md

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,3 @@ Fix a PPL bug end-to-end or follow up on an existing PR.
3030

3131
**Related files:** [`.claude/harness/ppl-bugfix-harness.md`](.claude/harness/ppl-bugfix-harness.md)
3232

33-
---
34-
35-
## `/dedupe`
36-
37-
Find duplicate GitHub issues for a given issue.
38-
39-
**Usage:**
40-
41-
```
42-
/dedupe 1234
43-
```
44-
45-
**What it does:**
46-
47-
1. Reads the target issue
48-
2. Runs 3+ parallel search strategies to find potential duplicates (only older issues)
49-
3. Verifies candidates by reading each one
50-
4. Posts a structured comment on the issue listing 1-3 confirmed duplicates (if any)
51-
52-
**Skips:** closed issues, broad feedback issues, issues already checked
53-
54-
**Related files:** [`scripts/comment-on-duplicates.sh`](scripts/comment-on-duplicates.sh)

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3205,7 +3205,7 @@ public RelNode visitChart(Chart node, CalcitePlanContext context) {
32053205
|| node.getColumnSplit() == null
32063206
|| Objects.equals(config.limit, 0)) {
32073207
// The output of chart is expected to be ordered by row split names
3208-
relBuilder.sort(relBuilder.field(0));
3208+
relBuilder.sort(relBuilder.nullsLast(relBuilder.field(0)));
32093209
return relBuilder.peek();
32103210
}
32113211

@@ -3275,7 +3275,8 @@ public RelNode visitChart(Chart node, CalcitePlanContext context) {
32753275
relBuilder.field(2))
32763276
.as(aggFieldName));
32773277
// The output of chart is expected to be ordered by row and column split names
3278-
relBuilder.sort(relBuilder.field(0), relBuilder.field(1));
3278+
relBuilder.sort(
3279+
relBuilder.nullsLast(relBuilder.field(0)), relBuilder.nullsLast(relBuilder.field(1)));
32793280
return relBuilder.peek();
32803281
}
32813282

core/src/main/java/org/opensearch/sql/expression/function/udf/binning/MinspanBucketFunction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.apache.calcite.rex.RexCall;
1515
import org.apache.calcite.sql.type.ReturnTypes;
1616
import org.apache.calcite.sql.type.SqlReturnTypeInference;
17+
import org.apache.calcite.sql.type.SqlTypeTransforms;
1718
import org.opensearch.sql.calcite.utils.PPLOperandTypes;
1819
import org.opensearch.sql.expression.function.ImplementorUDF;
1920
import org.opensearch.sql.expression.function.UDFOperandMetadata;
@@ -43,7 +44,7 @@ public MinspanBucketFunction() {
4344

4445
@Override
4546
public SqlReturnTypeInference getReturnTypeInference() {
46-
return ReturnTypes.VARCHAR_2000;
47+
return ReturnTypes.VARCHAR_2000.andThen(SqlTypeTransforms.FORCE_NULLABLE);
4748
}
4849

4950
@Override

core/src/main/java/org/opensearch/sql/expression/function/udf/binning/RangeBucketFunction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.apache.calcite.rex.RexCall;
1515
import org.apache.calcite.sql.type.ReturnTypes;
1616
import org.apache.calcite.sql.type.SqlReturnTypeInference;
17+
import org.apache.calcite.sql.type.SqlTypeTransforms;
1718
import org.opensearch.sql.calcite.utils.PPLOperandTypes;
1819
import org.opensearch.sql.expression.function.ImplementorUDF;
1920
import org.opensearch.sql.expression.function.UDFOperandMetadata;
@@ -47,7 +48,7 @@ public RangeBucketFunction() {
4748

4849
@Override
4950
public SqlReturnTypeInference getReturnTypeInference() {
50-
return ReturnTypes.VARCHAR_2000;
51+
return ReturnTypes.VARCHAR_2000.andThen(SqlTypeTransforms.FORCE_NULLABLE);
5152
}
5253

5354
@Override

core/src/main/java/org/opensearch/sql/expression/function/udf/binning/SpanBucketFunction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.apache.calcite.rex.RexCall;
1515
import org.apache.calcite.sql.type.ReturnTypes;
1616
import org.apache.calcite.sql.type.SqlReturnTypeInference;
17+
import org.apache.calcite.sql.type.SqlTypeTransforms;
1718
import org.opensearch.sql.calcite.utils.PPLOperandTypes;
1819
import org.opensearch.sql.expression.function.ImplementorUDF;
1920
import org.opensearch.sql.expression.function.UDFOperandMetadata;
@@ -41,7 +42,7 @@ public SpanBucketFunction() {
4142

4243
@Override
4344
public SqlReturnTypeInference getReturnTypeInference() {
44-
return ReturnTypes.VARCHAR_2000;
45+
return ReturnTypes.VARCHAR_2000.andThen(SqlTypeTransforms.FORCE_NULLABLE);
4546
}
4647

4748
@Override

integ-test/src/test/java/org/opensearch/sql/calcite/CalciteNoPushdownIT.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
CalciteAddColTotalsCommandIT.class,
2626
CalciteConvertCommandIT.class,
2727
CalciteArrayFunctionIT.class,
28+
CalciteBinChartNullIT.class,
2829
CalciteBinCommandIT.class,
2930
CalciteConvertTZFunctionIT.class,
3031
CalciteCsvFormatIT.class,

0 commit comments

Comments
 (0)