feat(sql): array_elem_{min,max,avg,sum} functions#6829
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@CodeRabbit please review critically |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 16719 🏁 Script executed: Length of output: 3824 🏁 Script executed: Length of output: 12956 🏁 Script executed: Length of output: 29845 🏁 Script executed: Length of output: 7313 🏁 Script executed: Length of output: 29851 🏁 Script executed: Length of output: 1690 🏁 Script executed: Length of output: 14 🏁 Script executed: Length of output: 1351 🏁 Script executed: Length of output: 44069 🏁 Script executed: Length of output: 11831 🏁 Script executed: Length of output: 2700 🏁 Script executed: Length of output: 1191 🏁 Script executed: Length of output: 771 🏁 Script executed: Length of output: 427 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 2361 🏁 Script executed: Length of output: 2738 🏁 Script executed: Length of output: 0
🔴 Critical: Integer Overflow VulnerabilitiesIssue 1: Unchecked overflow in stride calculationFile: public static int computeRowMajorStrides(int[] shape, int[] strides) {
int stride = 1;
for (int d = shape.length - 1; d >= 0; d--) {
strides[d] = stride;
stride *= shape[d]; // ⚠️ SILENT OVERFLOW
}
return stride;
}Problem: For a 3D array Required: Add overflow detection: if (stride > Integer.MAX_VALUE / shape[d]) {
throw CairoException.critical(0).put("array cardinality overflow");
}Issue 2: Unchecked overflow in flat index calculationFile: public static int coordsToFlatIndex(int[] coords, int[] strides) {
int fi = 0;
for (int d = 0; d < coords.length; d++) {
fi += coords[d] * strides[d]; // ⚠️ UNCHECKED ARITHMETIC
}
return fi;
}Problem: 🔴 Critical: Memory Allocation Without Bounds ValidationIssue 3: Unbounded allocation in GROUP BYFile: int capacity = Math.max(flatCardinality, (int) (flatCardinality * Math.pow(OVERALLOC_BASE, nDims)));
long blockSize = headerSize + (long) capacity * Double.BYTES;
long ptr = allocator.malloc(blockSize);Problem: This allocates memory before checking against Evidence: I verified that Required: Add explicit bounds check: if (capacity > configuration.maxArrayElementCount()) {
throw SqlException.position(0).put("array capacity exceeds limit");
}Issue 4: Overallocation multiplier grows exponentially with dimensionsFile: private static final double OVERALLOC_BASE = 1.5;Then: Problem:
For a 4D array with 10k elements, you're allocating 50k elements = 400KB when only 80KB is needed. Multiply this by thousands of groups and you're wasting gigabytes. Challenge: Why use Request: Provide memory usage benchmarks for 3D/4D arrays with high group cardinality. 🟡 Design Question: Kahan Compensation Merge SemanticsIssue 5: Potentially incorrect compensation mergeFile: protected void mergeOne(long destDataPtr, int destFi, double srcVal, int srcFi) {
// ...
if (Numbers.isFinite(destVal)) {
double srcComp = Unsafe.getUnsafe().getDouble(srcCompensationPtr + (long) srcFi * Double.BYTES);
double y = srcVal - srcComp; // ⚠️ IS THIS CORRECT?
double t = destVal + y;
Unsafe.getUnsafe().putDouble(compensationPtr + (long) destFi * Double.BYTES, (t - destVal) - y);
Unsafe.getUnsafe().putDouble(destAddr, t);
}
}Problem: You're treating Mathematical Question: In standard Kahan summation, the compensation tracks rounding error from adding values. When merging two pre-compensated sums:
Should you:
Request: Provide a mathematical proof or reference showing this merge formula preserves Kahan accuracy. If unsure, compare against the scalar 🟡 Missing Test Coverage for Edge CasesIssue 6: No overflow/bounds testsI searched for overflow tests: rg -l "overflow|Integer.MAX|Long.MAX" core/src/test/.../array/Result: Zero matches. Required tests:
Example test: `@Test`
public void testArraySizeExceedsLimit() throws Exception {
assertMemoryLeak(() -> {
execute("CREATE TABLE tab (arr DOUBLE[])");
try {
execute("INSERT INTO tab SELECT array_agg(x::double) FROM long_sequence(2000000)");
Assert.fail("Expected exception");
} catch (SqlException e) {
TestUtils.assertContains(e.getFlyweightMessage(), "exceeds limit");
}
});
}🟡 Performance & Complexity ConcernsIssue 7: Remap logic is complex and error-proneFile: private boolean checkNeedsRemap() {
if (nDims <= 1) {
return false;
}
ArrayView.computeRowMajorStrides(accShape, accStrides);
for (int d = 0; d < nDims; d++) {
if (accShape[d] > 1 && accStrides[d] != newStrides[d]) {
return true;
}
}
return false;
}Concern: This side-effect (populating Edge case: What if Request: Add a test for shape growth in middle dimensions with outer/inner dims unchanged, e.g.: `@Test`
public void testShapeGrowthMiddleDimension() throws Exception {
assertGroupByTyped("DOUBLE[][][]", "...",
"ARRAY[[[1.0, 2.0], [3.0, 4.0]]]", // shape [1, 2, 2]
"ARRAY[[[5.0, 6.0]], [[7.0, 8.0]], [[9.0, 10.0]]]" // shape [3, 1, 2] → grows dim 0
);
}Issue 8: Avg variable-mode transition scans entire array twiceFile: You scan the input array once to detect NaN (Lines 111-137), then scan again during accumulation (Lines 140+). For large arrays this is wasteful. Optimization: Combine the NaN detection with accumulation in a single pass. Set a flag if NaN is encountered, then transition to variable mode after the loop if needed. 🟢 Minor: Documentation & Code QualityIssue 9: Javadoc inheritance tags add noiseFiles like Suggestion: Revert these cosmetic changes. They belong in a separate "docs cleanup" PR, not mixed with a feature addition. Issue 10: Test name inconsistencySome tests use Minor: Add Javadoc to the base test class explaining when to use each helper. Summary of Required Actions
Bottom line: The core algorithm is sound, but the lack of bounds validation and overflow protection makes this unsafe for production. The Kahan merge semantics need verification. Add the missing tests and address the critical issues before merging. Would you like me to generate specific test cases or propose fixes for any of these issues? 🧠 Learnings used |
|
@CodeRabbit can you summarize what has been fixed and what has not? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 54 🏁 Script executed: Length of output: 557 🏁 Script executed: Length of output: 363 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 2272 🏁 Script executed: Length of output: 1298 🏁 Script executed: Length of output: 1261 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 29 🏁 Script executed: Length of output: 212 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 4012
✅ Fixed (3/10 issues)Issue 5: Kahan Merge Formula - ModifiedThe merge implementation was updated from: double y = srcVal - srcComp; // OLDto: double y = (srcVal - srcComp) - destComp; // NEWThis now subtracts both compensations, which is more correct for merging two Kahan-compensated sums. Issue 7: Middle-Dimension Growth Tests - Fixed ✓Tests were added in
These exercise the remap logic when inner dimensions grow while outer dimensions stay constant. Issue 9: JavaDoc Clutter - Fixed ✓The ❌ Not Fixed (7/10 issues)🔴 Issue 1: Integer Overflow in
|
| Status | Count | Issues |
|---|---|---|
| ✅ Fixed | 3 | Kahan merge formula, middle-dim tests, javadoc |
| ❌ Not Fixed | 7 | All critical overflow/bounds issues, missing tests, overallocation, double-scan |
⚠️ Bottom Line
The three most critical issues remain unaddressed:
- No overflow protection in
ArrayView.javastride/index calculations → crash risk - No bounds validation before GROUP BY allocation → OOM risk
- No tests for overflow/bounds edge cases → undetected failures in production
The code coverage is excellent (96.36%), but it tests happy paths, not attack surfaces. A single query with shape [100000, 100000] will crash with integer overflow.
Recommendation: Address Issues 1-3 before merging. These are production-critical safety checks, not nice-to-haves.
Would you like me to generate the specific overflow protection code and test cases for Issues 1-3?
🧠 Learnings used
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-06T18:22:28.130Z
Learning: In questdb/questdb, for PRs requested by javier, provide line-cited, push-back style reviews with concrete follow-ups (tests, diffs, and performance considerations).
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-06T18:22:28.130Z
Learning: For questdb/questdb, javier expects a rigorous, push-back style review; always include concrete line-cited findings and propose targeted tests/fixes when reviewing javier’s PRs.
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-26T12:33:03.277Z
Learning: In questdb/questdb, array functions do not need per-function size validation because `maxArrayElementCount()` (default 1M, configurable) is enforced upstream in `DirectArray.applyShape()` before any array function executes. This bound prevents unbounded heap allocations in array operations like sort, reverse, cumsum, etc.
Learnt from: jerrinot
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-04T11:18:36.492Z
Learning: For PRs by jerrinot, always use a thorough and critical review mode: question design decisions, challenge assumptions, scrutinize implementations for edge cases and potential issues, and avoid superficial validation. They prefer hard-core reviews that push back on the code.
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-24T14:27:11.921Z
Learning: In questdb/questdb, QuestDB currently only supports DOUBLE[] arrays for array group-by functions (`first_array`, `last_array`, and the upcoming `array_agg`). There are no INT[] or LONG[] array group-by functions.
...uestdb/test/griffin/engine/functions/groupby/AbstractDoubleArrayElemParallelGroupByTest.java
Outdated
Show resolved
Hide resolved
|
@CodeRabbit can you critically review this PR again, let me know what has been fixed and what's outstanding |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 27611 🏁 Script executed: Length of output: 11642 🏁 Script executed: Length of output: 1357 🏁 Script executed: Length of output: 8667 🏁 Script executed: Length of output: 1569 🏁 Script executed: Length of output: 3480 🏁 Script executed: Length of output: 25795
✅ Fixed Since Last Review (6/10)
❌ Still Outstanding (3/10)🔴 Issue 1: Integer Overflow in
|
| Priority | Issue | Status |
|---|---|---|
| 🔴 | Overflow defeats maxArrayElementCount guard |
❌ NOT FIXED — critical, must fix before merge |
| 🟡 | coordsToFlatIndex intermediate overflow |
❌ NOT FIXED — low risk, cheap to fix |
| 🟡 | No overflow tests | ❌ NOT FIXED |
| All others | — | ✅ Fixed |
The only blocker is Issue 1. The maxArrayElementCount guard in growAccumulator() is a good addition, but it's rendered ineffective by the integer overflow in computeRowMajorStrides(). Fix the overflow first, and Issue 6 test should be added to prove it.
🧠 Learnings used
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-26T12:33:03.277Z
Learning: In questdb/questdb, array functions do not need per-function size validation because `maxArrayElementCount()` (default 1M, configurable) is enforced upstream in `DirectArray.applyShape()` before any array function executes. This bound prevents unbounded heap allocations in array operations like sort, reverse, cumsum, etc.
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-06T18:22:28.130Z
Learning: In questdb/questdb, for PRs requested by javier, provide line-cited, push-back style reviews with concrete follow-ups (tests, diffs, and performance considerations).
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-06T18:22:28.130Z
Learning: For questdb/questdb, javier expects a rigorous, push-back style review; always include concrete line-cited findings and propose targeted tests/fixes when reviewing javier’s PRs.
Learnt from: jerrinot
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-04T11:18:36.492Z
Learning: For PRs by jerrinot, always use a thorough and critical review mode: question design decisions, challenge assumptions, scrutinize implementations for edge cases and potential issues, and avoid superficial validation. They prefer hard-core reviews that push back on the code.
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-06T18:22:28.130Z
Learning: In questdb/questdb, javier prefers hard‑core, critical reviews that challenge design choices and edge cases; respond with deep, push-back style analyses for javier’s requests.
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-24T14:27:11.921Z
Learning: In questdb/questdb, QuestDB currently only supports DOUBLE[] arrays for array group-by functions (`first_array`, `last_array`, and the upcoming `array_agg`). There are no INT[] or LONG[] array group-by functions.
Learnt from: mtopolnik
Repo: questdb/questdb PR: 5997
File: core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAccessFunctionFactory.java:127-131
Timestamp: 2025-07-31T08:21:17.390Z
Learning: In DoubleArrayAccessFunctionFactory.java, overflow validation for array index casting from long to int is performed in the validateIndexArgs() method rather than at each individual cast site. The validateIndexArgs() method is called early in newInstance() to validate all index arguments, making subsequent casts safe.
computeRowMajorStrides() used plain int multiplication which silently overflowed for large combined shapes, bypassing the maxArrayElementCount guard in growAccumulator(). Switch to long arithmetic with an explicit overflow check that throws CairoException. Add three tests: combined shape exceeding maxArrayElementCount, int cardinality overflow via computeNext, and int cardinality overflow via merge. Fix Harness to use try/finally for array cleanup on exception. Remove section-heading comments from test file. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@CodeRabbit i just pushed test you asked for, can you critically review and let me know what's left |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 11406 🏁 Script executed: Length of output: 18213 🏁 Script executed: Length of output: 1014
✅ All Issues Fixed🔴 Issue 1:
|
| Test | What it covers |
|---|---|
testCombinedShapeOverflowsIntCardinality |
Shape [50000,1] + [1,50000] → combined [50000,50000] = 2.5B elements, throws "array cardinality overflow" |
testMergeOverflowsIntCardinality |
Same overflow scenario triggered via parallel merge path |
testCombinedShapeExceedsMaxElementCount |
Custom maxArrayElementCount=100 config, combined shape of 121 elements, throws "array element count exceeds max" |
These tests are well-targeted and cover both the computeNext and merge code paths, as well as the separate maxArrayElementCount guard.
📊 Final Status: All Clear
| # | Issue | Status |
|---|---|---|
| 1 | Overflow defeats maxArrayElementCount guard |
✅ Fixed |
| 2 | coordsToFlatIndex intermediate overflow |
✅ Safe by construction |
| 3 | Unbounded allocation in GROUP BY | ✅ Fixed (prior push) |
| 4 | Exponential overallocation | ✅ Fixed (prior push) |
| 5 | Kahan merge formula | ✅ Fixed (prior push) |
| 6 | No overflow/bounds tests | ✅ Fixed |
| 7 | Middle-dim remap tests | ✅ Fixed (prior push) |
| 8 | Avg double-scan | ✅ Was not an issue |
| 9 | @inheritDoc spam |
✅ Was not an issue |
| 10 | Test name documentation | Minor, non-blocking |
The PR is ready to merge from a correctness and safety standpoint. No outstanding blockers remain.
🧠 Learnings used
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-26T12:33:03.277Z
Learning: In questdb/questdb, array functions do not need per-function size validation because `maxArrayElementCount()` (default 1M, configurable) is enforced upstream in `DirectArray.applyShape()` before any array function executes. This bound prevents unbounded heap allocations in array operations like sort, reverse, cumsum, etc.
Learnt from: mtopolnik
Repo: questdb/questdb PR: 5997
File: core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAccessFunctionFactory.java:127-131
Timestamp: 2025-07-31T08:21:17.390Z
Learning: In DoubleArrayAccessFunctionFactory.java, overflow validation for array index casting from long to int is performed in the validateIndexArgs() method rather than at each individual cast site. The validateIndexArgs() method is called early in newInstance() to validate all index arguments, making subsequent casts safe.
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-06T18:22:28.130Z
Learning: In questdb/questdb, for PRs requested by javier, provide line-cited, push-back style reviews with concrete follow-ups (tests, diffs, and performance considerations).
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-06T18:22:28.130Z
Learning: For questdb/questdb, javier expects a rigorous, push-back style review; always include concrete line-cited findings and propose targeted tests/fixes when reviewing javier’s PRs.
Learnt from: jerrinot
Repo: questdb/questdb PR: 5996
File: core/src/test/java/io/questdb/test/cutlass/http/line/LineHttpSenderTest.java:350-356
Timestamp: 2025-08-04T09:16:27.366Z
Learning: In QuestDB ILP tests, maximum dimension values like `(1 << 28) - 1` are intentionally hard-coded instead of using constants like `ArrayView.DIM_MAX_LEN` to serve as "canaries" that will cause test failures if the API limits change, ensuring developers are aware of breaking changes to the ILP API.
Learnt from: javier
Repo: questdb/questdb PR: 0
File: :0-0
Timestamp: 2026-02-24T14:27:11.921Z
Learning: In questdb/questdb, QuestDB currently only supports DOUBLE[] arrays for array group-by functions (`first_array`, `last_array`, and the upcoming `array_agg`). There are no INT[] or LONG[] array group-by functions.
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
[PR Coverage check]😍 pass : 672 / 701 (95.86%) file detail
|
Add element-wise array aggregate functions:
array_elem_min,array_elem_max,array_elem_sum,array_elem_avgDocs: questdb/documentation#382
Closes: #6789
Summary
DOUBLE[]arrays:array_elem_min,array_elem_max,array_elem_sum, andarray_elem_avgnullarray_elem_sumandarray_elem_avguse Kahan compensated summation for floating-point accuracyavgvariant uses a uniform/variable dual-mode count tracker to avoid per-element count allocation in the common casemerge()Test plan