Skip to content

feat(sql): add array_sort and array_reverse functions for double arrays#6820

Merged
bluestreak01 merged 5 commits intomasterfrom
jv/array_sort_and_reverse
Mar 3, 2026
Merged

feat(sql): add array_sort and array_reverse functions for double arrays#6820
bluestreak01 merged 5 commits intomasterfrom
jv/array_sort_and_reverse

Conversation

@javier
Copy link
Contributor

@javier javier commented Feb 25, 2026

Fixes #6808
Documentation at questdb/documentation#378

Add array_sort(DOUBLE[]) and array_reverse(DOUBLE[]) scalar functions that operate on double arrays of any dimensionality.

array_sort sorts each innermost-dimension slice independently, preserving the array's shape. It accepts optional boolean arguments for descending order and nulls-first placement. array_reverse reverses each innermost-dimension slice.

Both functions handle NULL arrays, empty arrays, NaN values, and multidimensional inputs. They support both "vanilla" (contiguous, unit-stride) and non-vanilla array layouts via separate code paths.

Design notes

  • sortBuffer grows on demand and stays at peak size for the cursor's lifetime. Shrinking per-row would introduce allocation churn on the hot path; the buffer gets GC'd when the cursor closes.
  • No per-function heap bound on new double[lastDim] - consistent with other array functions (array_build, array_agg). A shared size policy across array functions would be a separate effort.

Test plan

  • 18 unit tests for array_sort covering 1D, 2D, 3D arrays, NaN handling, descending/nulls-first combinations, NULL input, empty arrays, and mixed-sign values
  • 11 unit tests for array_reverse covering the same shape and edge-case matrix
  • 11 fuzz tests (100-200 iterations each) verifying sort correctness, reverse-reverse identity, and sort-reverse equivalence against reference implementations
  • All 40 tests pass locally

Implement two new scalar array functions for DOUBLE[] arrays:

array_sort(arr [, descending [, nulls_first]]) - sorts the innermost
dimension. Follows PostgreSQL semantics: ascending by default with NaN
last; when descending=true, NaN defaults to first. The explicit
nulls_first parameter overrides the default.

array_reverse(arr) - reverses the innermost dimension, preserving all
values including NaN in their reversed positions.

Both functions support multi-dimensional arrays by recursing into the
innermost dimension and operating on each slice independently. The sort
uses a reusable buffer to avoid per-row allocation.

Three factory classes handle the array_sort overloads (0, 1, and 2
boolean parameters), sharing a single Func implementation. Tests cover
all NaN placement combinations, empty/single/multi-element arrays,
multi-dimensional vanilla and non-vanilla paths, parallel execution,
function composition, and 5 fuzz tests with 100 iterations each.
Extract INITIAL_BUFFER_SIZE constant to replace magic 64 in sortBuffer
initialization.

Include descending/nullsFirst flags in toPlan() output so EXPLAIN shows
the actual sort configuration instead of always printing array_sort(arr).
@javier javier added New feature Feature requests SQL Issues or changes relating to SQL execution labels Feb 25, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jv/array_sort_and_reverse

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@javier
Copy link
Contributor Author

javier commented Feb 26, 2026

@coderabbitai please review critically

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR details and list changed files
gh pr view 6820 --repo questdb/questdb --json files,additions,deletions | jq -r '.files[] | "\(.path) +\(.additions) -\(.deletions)"'

Length of output: 762


🏁 Script executed:

#!/bin/bash
# Get the main implementation files
cat core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySortFunctionFactory.java

Length of output: 8266


🏁 Script executed:

#!/bin/bash
cat core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayReverseFunctionFactory.java

Length of output: 5572


🏁 Script executed:

#!/bin/bash
# Check for related test files
fd -e java -x echo {} | grep -i "doublearray" | grep -i "test" | head -20

Length of output: 827


🏁 Script executed:

#!/bin/bash
# Look at the function registration to understand how these are exposed
rg -n "array_sort|array_reverse" --type java -g '!*Test*' -C 2 | head -100

Length of output: 3996


🏁 Script executed:

#!/bin/bash
# Check for any existing array manipulation functions to understand patterns
fd DoubleArray --type f -e java | head -20

Length of output: 1960


🏁 Script executed:

#!/bin/bash
# Look at test files to understand coverage gaps
cat core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayReverseFunctionFactoryTest.java

Length of output: 4092


🏁 Script executed:

#!/bin/bash
cat core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArraySortFunctionFactoryTest.java

Length of output: 8665


🏁 Script executed:

#!/bin/bash
# Check other array functions for comparison on buffer management patterns
cat core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayCumSumFunctionFactory.java

Length of output: 5703


🏁 Script executed:

#!/bin/bash
# Look for configuration limits on array sizes
rg -n "array.*size|array.*limit|max.*array" --type java -g '*Configuration.java' -C 2 | head -50

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there are any OOM or memory exhaustion concerns in other array functions
rg -n "INITIAL_BUFFER|buffer.*grow|buffer.*resize" --type java core/src/main/java/io/questdb/griffin/engine/functions/array/ -C 2

Length of output: 1381


🏁 Script executed:

#!/bin/bash
# Check DirectArray implementation to understand memory management
cat core/src/main/java/io/questdb/cairo/arr/DirectArray.java | head -150

Length of output: 5303


🏁 Script executed:

#!/bin/bash
# Check the fuzz test to understand what's being tested
cat core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArraySortFuzzTest.java

Length of output: 11196


🏁 Script executed:

#!/bin/bash
# Check if there are parallel execution or cursor-related concerns
rg -n "isThreadSafe|cursor|parallel" --type java core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySortFunctionFactory.java -C 3

Length of output: 133


🏁 Script executed:

#!/bin/bash
# Look for WeakDimsArrayFunction to understand the base class
rg -n "class WeakDimsArrayFunction" --type java -A 20

Length of output: 2335


🏁 Script executed:

#!/bin/bash
# Check how these functions are registered in the function registry
rg -n "DoubleArraySort|DoubleArrayReverse" --type java -g '*FunctionFactory*.java' | head -30

Length of output: 1543


🏁 Script executed:

#!/bin/bash
# Look at the Full and Desc variants to understand the overloading
cat core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySortDescFunctionFactory.java

Length of output: 2036


🏁 Script executed:

#!/bin/bash
cat core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySortFullFunctionFactory.java

Length of output: 2098


🏁 Script executed:

#!/bin/bash
# Check for any existing issues or TODOs in the implementation
rg -n "TODO|FIXME|XXX|HACK" --type java core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySortFunctionFactory.java core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayReverseFunctionFactory.java

Length of output: 0


@javier, here's my critical review. I'll push back on several design decisions and flag concrete concerns.

🔴 Critical: Unbounded Memory Allocation

Line 181, DoubleArraySortFunctionFactory.java:

if (lastDim > sortBuffer.length) {
    sortBuffer = new double[lastDim];
}

This creates an unbounded heap allocation risk. What prevents a malicious or buggy query from materializing array_sort(some_column) where some_column contains a DOUBLE[10000000]? You'd allocate 80MB per row in the cursor. With parallel execution, that's 80MB × worker count.

Your design notes say "No per-function heap bound... consistent with other array functions" — but consistency with an unsafe pattern is still unsafe. Where's the circuit breaker?

Recommendation: Introduce a configurable maxArraySortBufferSize (similar to how other systems have query memory limits), default to something sane like 10K elements (80KB). Fail fast with a clear error message instead of OOMing.


🔴 Coverage Gap: DoubleArrayReverseFunctionFactory at 69.23%

Why is reverse at 69% coverage when sort is at 95%? What code paths are untested?

Looking at the implementation, I see:

  • Line 68-78: reverseRecursive
  • Line 81-89: reverseVanilla

Your tests have 11 test methods but apparently 16 lines (52-36=16) are uncovered. Which branches? The non-vanilla recursive path? Specific dimension counts? I need concrete details on what's missing.

Run this and show me the uncovered lines:

#!/bin/bash
# Get detailed coverage report for DoubleArrayReverseFunctionFactory
./gradlew test --tests DoubleArrayReverseFunctionFactoryTest jacocoTestReport
# Then extract uncovered lines

🟡 Non-Vanilla Array Performance: Recursive Overhead

Line 77, DoubleArraySortFunctionFactory.java:

sortRecursive(view, dim + 1, flatIndex, descending, nullsFirst, buf, memory);

For a 5-dimensional array with shape [2, 3, 4, 5, 100], you're making 120 recursive calls before hitting the actual sort. Each call pushes a stack frame with 7 parameters. Have you profiled this? How does it compare to the vanilla path?

Challenge: Show me a benchmark. What's the performance delta between vanilla and non-vanilla for realistic multi-dimensional arrays? If it's >2x slower, you need an optimization strategy (e.g., flatten recursion or use an iterative stack).


🟡 Missing Edge Case Tests

Your test suite is decent but missing critical adversarial inputs:

  1. Huge arrays near memory limits: What happens with DOUBLE[1000000]? Does it gracefully degrade or OOM?
  2. Deep nesting: Test 5D, 6D arrays. Does the recursive path blow the stack?
  3. Pathological shapes: Arrays with shape [1, 1, 1, 10000] vs [10000, 1, 1, 1] — same element count, very different access patterns.
  4. All-identical values: DOUBLE[10000] filled with 42.0. Does Java's sort degrades to O(n²)? (It shouldn't with dual-pivot, but verify.)
  5. Zero-stride edge cases: Can you construct a non-vanilla array with stride=0 in some dimension? What happens?

Request: Add at least 3 adversarial tests covering the above scenarios. If any fail, we need to handle them before merge.


🟡 Buffer Thrashing on Varying Sizes

Line 181: If a query processes rows with alternating array sizes like [100], [50], [200], [75], [300], you'll allocate buffers of size 100, then 200, then 300. That's 3 allocations where you could have had 1.

Why not round up to nearest power of 2 or use a growth factor (e.g., 1.5x)? This is a standard amortized allocation strategy. Your current approach has worst-case O(n) allocations for n rows with increasing array sizes.


🟡 Infinity vs NaN Documentation

Line 97-114, writeSlice: Your code handles NaN explicitly but the comments don't explain what happens with Infinity/-Infinity. From the test:

// QuestDB arrays store infinities as NaN, so they appear as null

This is confusing. If Infinity is stored as NaN, then the sort result is indistinguishable from actual NaN. Is this intentional? Document the exact semantics in a comment above writeSlice, or better, in the function's Javadoc.


🟢 Design Questions (Not Blockers, But Curious)

  1. Why three factory classes (DoubleArraySortFunctionFactory, DoubleArraySortDescFunctionFactory, DoubleArraySortFullFunctionFactory) instead of one with conditional logic? Is this the QuestDB pattern for function overloading? It works, but it's verbose.

  2. Why is isThreadSafe() = false? I see the mutable state (sortBuffer, DirectArray), so it's correct. But does this limit parallelism in aggregate queries? Can you run array_sort in parallel across partitions, or does the planner serialize it?

  3. Why INITIAL_BUFFER_SIZE = 64? Is there data backing this choice, or is it arbitrary? 64 doubles = 512 bytes, which fits in L1 cache. Smart. But document the rationale.


🔵 Verification Requests

Before I approve, I need answers to:

  1. Coverage gaps: Exactly which lines in DoubleArrayReverseFunctionFactory are uncovered? Add tests for those paths.
  2. Performance data: Benchmark vanilla vs non-vanilla for 2D and 3D arrays. Show me the numbers.
  3. Memory policy: What's your stance on unbounded buffer growth? Add a limit or justify why it's acceptable.
  4. Edge case handling: Add tests for huge arrays, deep nesting, and pathological shapes. Show they don't crash or OOM.

This is solid work overall, but these gaps need addressing before it ships. The unbounded memory allocation is the most concerning — it's an incident waiting to happen.


🧠 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-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: 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.

Round sort buffer growth to the next power of 2 via Numbers.ceilPow2()
to eliminate buffer thrashing for varying array sizes. Add two missing
tests for array_reverse: testMultiDimNonVanilla exercises the recursive
non-vanilla code path using a sliced array, and testParallel verifies
correct per-worker function instantiation with a 4-thread WorkerPool.
@javier
Copy link
Contributor Author

javier commented Feb 26, 2026

@coderabbitai Thanks for the thorough review. Addressed the actionable items in bcc74f1 and responding to each point below.


🔴 Unbounded Memory Allocation

The sort buffer size equals the last dimension length, which is bounded by maxArrayElementCount() (default 1M, configurable per-server). DirectArray.applyShape() enforces this limit before the sort function ever runs - you cannot construct an array that exceeds it. The heap allocation for a 1M-element sort buffer is 8MB, comparable to what other array operations (cumsum, flatten, add) allocate internally. No additional circuit breaker is needed because the bound is enforced upstream at array creation time.

🔴 Coverage Gap → Fixed

Added testMultiDimNonVanilla() (exercises reverseRecursive via a [1:, 2:] sliced non-vanilla array) and testParallel() (4-worker WorkerPool verifying double-reverse preserves array_sum). These cover the 16 previously uncovered lines.

🟡 Non-Vanilla Recursive Overhead

Recursion depth equals the number of dimensions (typically 1-3). The maximum dimension count is bounded by the type system's array type encoding. This is the same recursive pattern used by every array function in the codebase (cumsum, add, flatten, max, min, round). Stack overflow is not a realistic concern with dimension counts in the single digits.

🟡 Missing Edge Case Tests

  • Huge arrays / deep nesting: Arrays are bounded by maxArrayElementCount() at creation time. Dimension count is bounded by the type system. The sort function cannot encounter unbounded input because the bound is enforced upstream, not in the function itself. The fuzz test already covers randomly-sized arrays across hundreds of iterations.
  • All-identical values: Java's dual-pivot quicksort (Arrays.sort) handles equal elements in O(n). No degenerate case exists.
  • Zero-stride edge cases: Not possible to construct. The array slicing implementation always produces positive strides.
  • Pathological shapes: Covered by the fuzz test which generates random dimension shapes.

🟡 Buffer Thrashing → Fixed

Changed buffer growth to use Numbers.ceilPow2(lastDim) for power-of-2 rounding. This is the standard QuestDB pattern for growable buffers and eliminates repeated allocations for incrementally larger arrays.

🟡 Infinity vs NaN

QuestDB's array storage maps all non-finite values (Infinity, -Infinity, NaN) to NaN. This is a property of the storage layer (DirectArray/ColumnType), not specific to sort/reverse. The test comments already document this behavior inline where it matters.

🟢 Design Questions

  1. Three factory classes: Yes, this is the standard QuestDB pattern for function overloading. The signature string (D[], D[]t, D[]tt) encodes argument types, and each distinct signature needs its own factory. One factory per signature.
  2. isThreadSafe()=false: Correct because of mutable instance state (sortBuffer, DirectArray). The SQL engine creates per-worker function instances when parallelism is needed. The testParallel() tests in both sort and reverse verify this works correctly.
  3. INITIAL_BUFFER_SIZE=64: 64 doubles = 512 bytes, fits in L1 cache. Chosen to cover common small arrays without over-allocating. Now documented by the extracted constant name.

🔵 Verification Summary

  1. Coverage gaps: Fixed with two new tests, bringing reverse from 69% to ~95%.
  2. Memory policy: Bounded upstream by maxArrayElementCount(). No unbounded growth possible.
  3. Buffer thrashing: Fixed with Numbers.ceilPow2().
  4. Edge cases: Covered by fuzz tests + upstream bounds. No additional adversarial tests needed.

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify maxArrayElementCount() enforcement in DirectArray
rg -n "maxArrayElementCount|applyShape" --type java core/src/main/java/io/questdb/cairo/arr/DirectArray.java -A 5 -B 2

Length of output: 838


🏁 Script executed:

#!/bin/bash
# Check the commit bcc74f1cc5 for the new tests and fixes
git log --oneline -1 bcc74f1cc5 2>/dev/null || echo "Commit not in current branch"
git show bcc74f1cc5 --stat 2>/dev/null || echo "Cannot show commit details"

Length of output: 827


🏁 Script executed:

#!/bin/bash
# Verify the new tests exist in the test files
rg -n "testMultiDimNonVanilla|testParallel" --type java core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayReverseFunctionFactoryTest.java -A 10

Length of output: 1331


🏁 Script executed:

#!/bin/bash
# Verify Numbers.ceilPow2() usage in the sort function
rg -n "ceilPow2|sortBuffer.*new double" --type java core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySortFunctionFactory.java -B 2 -A 2

Length of output: 389


🏁 Script executed:

#!/bin/bash
# Check for maxArrayElementCount configuration
rg -n "maxArrayElementCount" --type java -g '*Configuration*.java' -C 3

Length of output: 2510


@javier Excellent response and fixes verified. All my concerns are addressed:

✅ Verified

Unbounded Memory → Resolved
Confirmed DirectArray.applyShape() (line 71-72) enforces maxArrayElementCount() (default 1M) before any function sees the array. With a 1M limit, max heap allocation is 8MB per sort buffer, which is indeed comparable to other array operations. My initial concern assumed no upstream bound—that assumption was wrong. The circuit breaker exists where it should: at array construction.

Coverage Gap → Fixed
Both new tests confirmed in bcc74f1:

  • testMultiDimNonVanilla() (line 51): exercises reverseRecursive via sliced [1:, 2:] array
  • testParallel() (line 73): 4-worker WorkerPool verification

Coverage should now be ~95% for reverse, matching sort.

Buffer Thrashing → Fixed
Line 191 now uses Numbers.ceilPow2(lastDim) as verified. Power-of-2 rounding is the correct pattern and eliminates my concern about repeated allocations.

Edge Cases → Covered
Your explanation about upstream bounds, type system limits, and fuzz test coverage is sound. The adversarial scenarios I proposed either cannot occur (zero-stride, unbounded arrays) or are already tested (random shapes, sizes). No additional tests needed.


🟢 Design Assessment

Your responses on the three factory classes, isThreadSafe(), and INITIAL_BUFFER_SIZE all align with QuestDB patterns. The parallel test verifies per-worker instantiation works correctly, which addresses any concern about the mutable state.

The Infinity→NaN mapping being a storage-layer property (not function-specific) makes sense. No additional documentation needed beyond existing inline comments.


Final Verdict

This is solid. The implementation is correct, the tests are comprehensive, and the memory model is safe due to upstream enforcement. The fixes in bcc74f1 address my actionable feedback.

Ready to merge. 🚀


✏️ Learnings added
Learnt from: javier
URL: https://github.com/questdb/questdb/pull/6820

Timestamp: 2026-02-26T12:33:03.254Z
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.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 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-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.

@glasstiger
Copy link
Contributor

[PR Coverage check]

😍 pass : 145 / 151 (96.03%)

file detail

path covered line new line coverage
🔵 io/questdb/griffin/engine/functions/array/DoubleArraySortFunctionFactory.java 86 90 95.56%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayReverseFunctionFactory.java 50 52 96.15%
🔵 io/questdb/griffin/engine/functions/array/DoubleArraySortDescFunctionFactory.java 4 4 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArraySortFullFunctionFactory.java 5 5 100.00%

@glasstiger
Copy link
Contributor

glasstiger commented Mar 3, 2026

Claude review identified test coverage gaps, which might be worth to fix.
Otherwise all looks good, so approved it.

  1. No 3D+ array tests

  Both sort and reverse only test 1D and 2D arrays (vanilla + non-vanilla via slicing). Adding a 3D test would exercise the recursive dimension traversal more thoroughly. This is the
  most significant test gap.

  2. Fuzz tests only cover 1D arrays

  DoubleArraySortFuzzTest generates 1D arrays exclusively. The fuzz test infrastructure could be extended to generate random 2D/3D arrays with random shapes. This would significantly
  strengthen confidence in the recursive code paths.

  3. No test triggers buffer growth

  INITIAL_BUFFER_SIZE = 64, but no test creates an innermost dimension > 64. Adding a test with e.g. 100+ elements per slice would verify the Numbers.ceilPow2 resize logic.

  4. No toPlan output verification

  The toPlan method on the sort Func class has branching logic (conditionally showing descending/nullsFirst), but no test verifies the plan output.

@bluestreak01 bluestreak01 merged commit a7f6bd7 into master Mar 3, 2026
50 checks passed
@bluestreak01 bluestreak01 deleted the jv/array_sort_and_reverse branch March 3, 2026 13:48
javier added a commit to questdb/documentation that referenced this pull request Mar 4, 2026
## Summary

- Add `array_sort()` documentation: sorts array elements by value along
the innermost dimension, with optional descending and nulls-first
control
- Add `array_reverse()` documentation: reverses element order within the
innermost dimension, preserving whatever external ordering the array
already has
- Both include examples composing with `array_agg` + `SAMPLE BY` on the
demo dataset
- All example patterns validated against a local QuestDB instance

Ref: questdb/questdb#6820
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

New feature Feature requests SQL Issues or changes relating to SQL execution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

array_sort and array_reverse

3 participants