Skip to content

feat(sql): array_stddev(), array_min(), array_max() functions#5992

Merged
bluestreak01 merged 17 commits intomasterfrom
mt_array-stddev
Aug 1, 2025
Merged

feat(sql): array_stddev(), array_min(), array_max() functions#5992
bluestreak01 merged 17 commits intomasterfrom
mt_array-stddev

Conversation

@mtopolnik
Copy link
Copy Markdown
Contributor

@mtopolnik mtopolnik commented Jul 28, 2025

Summary by CodeRabbit

  • New Features

    • Added SQL functions for calculating minimum, maximum, and standard deviation (sample and population) of double arrays, supporting both flat and multi-dimensional arrays.
    • Introduced efficient recursive and flat-array handling for array math functions, including min, max, sum, average, count, cumulative sum, and various scalar operations.
  • Bug Fixes

    • Improved handling of special values (NaN, Infinity) in array aggregation functions to ensure correct results.
  • Refactor

    • Simplified and unified array function implementations, replacing interface-based callbacks with explicit recursion and iteration for better maintainability and performance.
    • Consolidated array shape and memory preparation logic for consistency across array operations.
  • Tests

    • Added comprehensive tests for new and existing array functions, covering edge cases, multi-dimensional arrays, and integration with table data.
  • Chores

    • Updated documentation and code formatting for clarity and consistency.
    • Enhanced .gitignore to exclude specific files.

@mtopolnik mtopolnik requested a review from nwoolmer July 28, 2025 14:07
@mtopolnik mtopolnik changed the title feat(sql): array_stddev() function feat(sql): array_stddev(), array_min(), array_max() functions Jul 28, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jul 31, 2025

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.

Walkthrough

This change set introduces new SQL functions for computing the maximum, minimum, and standard deviation (sample and population) of double arrays, including support for multi-dimensional arrays. It refactors array arithmetic and aggregation functions to use explicit recursive traversal instead of interface-based callbacks, removes legacy operator base classes and interfaces, consolidates array preparation logic, and adds comprehensive tests for the new features.

Changes

Cohort / File(s) Change Summary
ArrayView shape logic and accessors
core/src/main/java/io/questdb/cairo/arr/ArrayView.java
Renamed and inverted shapeEquals to shapeDiffers, updated usages, and added getHi()/getLo() methods for flat view range access.
DirectArray preparation
core/src/main/java/io/questdb/cairo/arr/DirectArray.java
Added prepare(int type, ArrayView arr) method to set type, copy shape, allocate memory, and return a MemoryA instance.
FlatArrayView min/max
core/src/main/java/io/questdb/cairo/arr/FlatArrayView.java
Added default methods maxDouble and minDouble to compute finite max/min double values over a range.
Array arithmetic and aggregation refactor
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAddScalarFunctionFactory.java,
DoubleArrayAndScalarDotProductFunctionFactory.java,
DoubleArrayAvgFunctionFactory.java,
DoubleArrayBinaryOperator.java,
DoubleArrayCountFunctionFactory.java,
DoubleArrayCumSumFunctionFactory.java,
DoubleArrayDivScalarFunctionFactory.java,
DoubleArrayDotProductFunctionFactory.java,
DoubleArrayMultiplyScalarFunctionFactory.java,
DoubleArrayRoundFunctionFactory.java,
DoubleArrayShiftDefaultNaNFunctionFactory.java,
DoubleArrayShiftFunctionFactory.java,
DoubleArrayStdDevFunctionFactory.java,
DoubleArrayStdDevPopFunctionFactory.java,
DoubleArrayStdDevSampFunctionFactory.java,
DoubleArraySubtractScalarFunctionFactory.java,
DoubleArraySumFunctionFactory.java,
DoubleNegArrayFunctionFactory.java,
DoubleScalarDivArrayFunctionFactory.java,
DoubleScalarSubtractArrayFunctionFactory.java
Refactored to remove DoubleUnaryArrayAccessor and legacy operator base classes. Introduced explicit recursive traversal for multi-dimensional arrays, restructured function classes to use new interfaces, added new min/max/stddev functions, and consolidated array preparation logic.
Removed legacy operator base classes and interfaces
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAndScalarDoubleArrayOperator.java,
DoubleArrayAndScalarIntArrayOperator.java,
DoubleUnaryArrayAccessor.java
Deleted base classes and interface for array-scalar operators and unary array access, removing related methods.
ArrayConstant construction
core/src/main/java/io/questdb/griffin/engine/functions/constants/ArrayConstant.java
Replaced manual array setup with a call to array.prepare(type, arrayIn) for shape and memory preparation.
Group-by stddev factories formatting
core/src/main/java/io/questdb/griffin/engine/functions/groupby/StdDevPopGroupByFunctionFactory.java,
StdDevSampleGroupByFunctionFactory.java
Reformatted newInstance method signatures for readability; no logic changes.
.gitignore update
.gitignore
Appended CLAUDE.md to ignored files.
New min/max function tests
core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayMaxFunctionFactoryTest.java,
DoubleArrayMinFunctionFactoryTest.java
Added comprehensive tests for array_max and array_min functions, covering edge cases and multi-dimensional arrays.
New stddev function tests
core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayStdDevFunctionFactoryTest.java
Added tests for array_stddev, array_stddev_samp, and array_stddev_pop functions, including group-by and edge cases.
Round function test minor edit
core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayRoundFunctionFactoryTest.java
Removed a comment line; no functional changes.

Sequence Diagram(s)

Example: array_max(D[]) Function Flow

sequenceDiagram
    participant SQLUser
    participant DoubleArrayMaxFunctionFactory
    participant Func
    participant Record
    participant ArrayView

    SQLUser->>DoubleArrayMaxFunctionFactory: Request array_max(D[])
    DoubleArrayMaxFunctionFactory->>Func: Create Func instance
    SQLUser->>Func: getDouble(Record)
    Func->>Record: Retrieve array argument
    Record-->>Func: Return ArrayView
    alt ArrayView is null
        Func-->>SQLUser: Return Double.NaN
    else ArrayView is vanilla
        Func->>ArrayView: flatView().maxDouble(lo, length)
        ArrayView-->>Func: Return max value
        Func-->>SQLUser: Return max value
    else
        Func->>Func: calculateRecursive(ArrayView, dim, flatIndex)
        Func-->>SQLUser: Return max value
    end
Loading

Example: DoubleArrayAddScalarFunctionFactory (Refactored)

sequenceDiagram
    participant SQLUser
    participant DoubleArrayAddScalarFunctionFactory
    participant Func
    participant Record
    participant ArrayView
    participant DirectArray

    SQLUser->>DoubleArrayAddScalarFunctionFactory: Request array + scalar
    DoubleArrayAddScalarFunctionFactory->>Func: Create Func instance
    SQLUser->>Func: getArray(Record)
    Func->>Record: Retrieve array and scalar arguments
    Record-->>Func: Return ArrayView and scalar
    Func->>DirectArray: prepare(type, ArrayView)
    DirectArray-->>Func: Return MemoryA
    alt ArrayView is vanilla
        Func->>Func: Loop over flat indices, add scalar
    else
        Func->>Func: calculateRecursive(ArrayView, dim, flatIndex)
    end
    Func-->>SQLUser: Return result ArrayView
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90 minutes

Poem

In the warren of code where the arrays do play,
Rabbits hop through dimensions, finding max and min each day.
With sums and means and standard dev,
They burrow deep, their tests to prep.
Old tunnels closed, recursion anew—
Oh, what wonders a clever hop can do!
🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mt_array-stddev

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (2)
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAndScalarDotProductFunctionFactory.java (1)

79-101: Remove redundant null check.

The array null check on line 89 is redundant since it's already checked on lines 80-82. This creates unnecessary nesting and confusion in the control flow.

 ArrayView arr = leftArg.getArray(rec);
 if (arr.isNull()) {
     return Double.NaN;
 }
 scalar = rightArg.getDouble(rec);
 if (Numbers.isNull(scalar)) {
     return Double.NaN;
 }
 value = 0d;
 
-if (!arr.isNull()) {
-    if (arr.isVanilla()) {
-        FlatArrayView flatView = arr.flatView();
-        for (int i = arr.getLo(), n = arr.getHi(); i < n; i++) {
-            double v = flatView.getDoubleAtAbsIndex(i);
-            if (Numbers.isFinite(v)) {
-                value += v * scalar;
-            }
+if (arr.isVanilla()) {
+    FlatArrayView flatView = arr.flatView();
+    for (int i = arr.getLo(), n = arr.getHi(); i < n; i++) {
+        double v = flatView.getDoubleAtAbsIndex(i);
+        if (Numbers.isFinite(v)) {
+            value += v * scalar;
         }
-    } else {
-        calculateRecursive(arr, 0, 0);
     }
+} else {
+    calculateRecursive(arr, 0, 0);
 }
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayCumSumFunctionFactory.java (1)

84-109: Remove redundant null check and consolidate duplicate logic.

The code has a redundant null check on line 96 (array was already checked on line 85) and duplicate logic for checking if no non-null values were found (lines 103-106 and 110-113).

 ArrayView arr = arrayArg.getArray(rec);
 if (arr.isNull()) {
     array.ofNull();
     return array;
 }
 
 currentSum = Double.NaN;
 compensation = 0d;
 array.setType(getType());
 array.setDimLen(0, arr.getCardinality());
 array.applyShape();
 memory = array.startMemoryA();
-if (arr.isNull()) {
-    array.ofNull();
-} else if (arr.isVanilla()) {
+if (arr.isVanilla()) {
     FlatArrayView flatView = arr.flatView();
     for (int i = arr.getLo(), n = arr.getHi(); i < n; i++) {
         accumulate(flatView.getDoubleAtAbsIndex(i));
     }
-    if (compensation == 0d && Numbers.isNull(currentSum)) {
-        // no non-null values so return null
-        array.ofNull();
-    }
 } else {
     calculateRecursive(arr, 0, 0);
 }
 if (compensation == 0d && Numbers.isNull(currentSum)) {
     // no non-null values so return null
     array.ofNull();
 }
🧹 Nitpick comments (7)
.gitignore (2)

50-50: Clarify whether gorunner is a file or directory

If gorunner is a directory produced by the Go test harness, append a trailing slash to avoid unintentionally ignoring any future file named gorunner elsewhere in the tree.

-compat/src/test/golang/gorunner
+compat/src/test/golang/gorunner/

51-51: Anchor the ignore rule to the repository root

Prefixing the pattern with a slash ensures only the top-level CLAUDE.md is ignored, not any file with the same name nested inside sub-directories.

-CLAUDE.md
+/CLAUDE.md
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevPopFunctionFactory.java (1)

64-66: Add @OverRide annotation to getName() method.

The getName() method overrides the parent class method and should have the @Override annotation for consistency and compile-time verification.

+@Override
 public String getName() {
     return FUNCTION_NAME;
 }
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleNegArrayFunctionFactory.java (1)

97-99: Add @OverRide annotation to getName() method.

The getName() method should have the @Override annotation for consistency with other overridden methods.

+@Override
 public String getName() {
     return OPERATOR_NAME;
 }
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAvgFunctionFactory.java (1)

83-86: Handle division by zero when no finite values are found.

When the array contains only non-finite values (NaN, Infinity), count will be 0, resulting in division by zero and returning NaN. While this may be intentional, it should be explicitly handled for clarity.

 count = 0;
 sum = 0d;
 calculateRecursive(arr, 0, 0);
-return sum / count;
+return count > 0 ? sum / count : Double.NaN;
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevSampFunctionFactory.java (1)

115-119: Consider using epsilon for floating-point comparison

While setting negative variance to 0 handles precision issues, consider using an epsilon comparison for more robust handling:

-if (variance < 0d) {
+if (variance < -1e-10) {
+    // Log warning - significant negative variance indicates numerical issues
+    variance = 0d;
+} else if (variance < 0d) {
     variance = 0d;
 }
core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayStdDevFunctionFactoryTest.java (1)

443-470: Consider adding test for large arrays

While the random data tests are good, consider adding a test with a larger array to verify performance and numerical stability:

@Test
public void testLargeArrayNumericalStability() throws SqlException {
    // Test with 10000 elements to verify numerical stability
    execute("CREATE TABLE large_array_test AS (" +
            "SELECT ARRAY_AGG(x) as arr FROM (" +
            "  SELECT x::double FROM long_sequence(10000)" +
            "))");
    
    // Verify the function completes without numerical issues
    assertSql(
        "count\n1\n",
        "SELECT count(*) FROM (SELECT array_stddev(arr) FROM large_array_test WHERE array_stddev(arr) IS NOT NULL)"
    );
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0235583 and 5c2e14a.

📒 Files selected for processing (36)
  • .gitignore (1 hunks)
  • core/src/main/java/io/questdb/cairo/arr/ArrayView.java (3 hunks)
  • core/src/main/java/io/questdb/cairo/arr/DirectArray.java (1 hunks)
  • core/src/main/java/io/questdb/cairo/arr/FlatArrayView.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAddScalarFunctionFactory.java (2 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAndScalarDotProductFunctionFactory.java (3 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAndScalarDoubleArrayOperator.java (0 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAndScalarIntArrayOperator.java (0 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAvgFunctionFactory.java (2 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayBinaryOperator.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayCountFunctionFactory.java (3 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayCumSumFunctionFactory.java (3 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayDivScalarFunctionFactory.java (2 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayDotProductFunctionFactory.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMaxFunctionFactory.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMinFunctionFactory.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMultiplyScalarFunctionFactory.java (2 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayRoundFunctionFactory.java (8 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayShiftDefaultNaNFunctionFactory.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayShiftFunctionFactory.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevFunctionFactory.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevPopFunctionFactory.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevSampFunctionFactory.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySubtractScalarFunctionFactory.java (2 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySumFunctionFactory.java (2 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleNegArrayFunctionFactory.java (3 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleScalarDivArrayFunctionFactory.java (2 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleScalarSubtractArrayFunctionFactory.java (2 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleUnaryArrayAccessor.java (0 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/constants/ArrayConstant.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/groupby/StdDevPopGroupByFunctionFactory.java (1 hunks)
  • core/src/main/java/io/questdb/griffin/engine/functions/groupby/StdDevSampleGroupByFunctionFactory.java (1 hunks)
  • core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayMaxFunctionFactoryTest.java (1 hunks)
  • core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayMinFunctionFactoryTest.java (1 hunks)
  • core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayRoundFunctionFactoryTest.java (0 hunks)
  • core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayStdDevFunctionFactoryTest.java (1 hunks)
💤 Files with no reviewable changes (4)
  • core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayRoundFunctionFactoryTest.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAndScalarIntArrayOperator.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAndScalarDoubleArrayOperator.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleUnaryArrayAccessor.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: mtopolnik
PR: questdb/questdb#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.
📚 Learning: in doublearrayaccessfunctionfactory.java, overflow validation for array index casting from long to i...
Learnt from: mtopolnik
PR: questdb/questdb#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.

Applied to files:

  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayDotProductFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayBinaryOperator.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayShiftFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayShiftDefaultNaNFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/constants/ArrayConstant.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevFunctionFactory.java
  • core/src/main/java/io/questdb/cairo/arr/ArrayView.java
  • core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayMaxFunctionFactoryTest.java
  • core/src/main/java/io/questdb/cairo/arr/FlatArrayView.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayCumSumFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayCountFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevPopFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMinFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAvgFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMaxFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySumFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleScalarDivArrayFunctionFactory.java
  • core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayMinFunctionFactoryTest.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayDivScalarFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMultiplyScalarFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySubtractScalarFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleNegArrayFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAndScalarDotProductFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAddScalarFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleScalarSubtractArrayFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevSampFunctionFactory.java
  • core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayRoundFunctionFactory.java
  • core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayStdDevFunctionFactoryTest.java
🔇 Additional comments (40)
core/src/main/java/io/questdb/cairo/arr/FlatArrayView.java (1)

196-224: LGTM! Well-implemented min/max aggregation methods.

The implementation correctly handles edge cases:

  • Uses Numbers.isFinite() to filter out NaN and infinity values
  • Returns Double.NaN when no finite values are found
  • Properly initializes with positive/negative infinity for comparison
  • Consistent with the existing avgDouble and sumDouble patterns
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayDotProductFunctionFactory.java (1)

93-93: LGTM! Method rename improves code clarity.

The change from !left.shapeEquals(right) to left.shapeDiffers(right) makes the intent more explicit while maintaining the same semantic behavior - throwing an exception when array shapes differ.

core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayShiftFunctionFactory.java (1)

143-143: LGTM! Clean refactoring consolidates array preparation.

The change to use array.prepare(getType(), arr) consolidates the type setting, shape copying, and memory allocation into a single method call, reducing code duplication and improving maintainability.

core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayShiftDefaultNaNFunctionFactory.java (1)

85-85: LGTM! Consistent refactoring improves code uniformity.

Same beneficial refactoring as seen in other array functions - consolidating array preparation into the prepare() method improves consistency and maintainability across the codebase.

core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayBinaryOperator.java (1)

80-87: LGTM! Method rename improves broadcasting logic clarity.

The three changes from shapeEquals to shapeDiffers make the broadcasting logic more explicit:

  • Line 80: Triggers broadcasting when shapes differ
  • Line 82: Broadcasts left array when it differs from output shape
  • Line 87: Broadcasts right array when it differs from output shape

The semantic behavior remains identical while improving code readability.

core/src/main/java/io/questdb/cairo/arr/DirectArray.java (1)

112-125: LGTM! Well-designed consolidation method.

The new prepare method effectively consolidates array preparation steps into a single, well-documented helper method. This reduces code duplication and provides a cleaner API for setting up arrays with a specific type and shape.

core/src/main/java/io/questdb/griffin/engine/functions/constants/ArrayConstant.java (1)

52-53: LGTM! Clean refactoring using the new prepare method.

The constructor now uses the consolidated prepare method instead of manual array setup steps, making the code more concise and consistent with other array function implementations in this PR.

core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevFunctionFactory.java (1)

27-33: LGTM! Proper alias implementation for array_stddev.

This class correctly implements array_stddev as an alias for array_stddev_samp by extending the sample standard deviation factory and only overriding the signature. This follows the established pattern for providing both population and sample standard deviation variants.

core/src/main/java/io/questdb/griffin/engine/functions/groupby/StdDevPopGroupByFunctionFactory.java (1)

49-55: LGTM! Improved formatting for better readability.

The multi-line parameter formatting improves readability of the method signature without changing functionality.

core/src/main/java/io/questdb/griffin/engine/functions/groupby/StdDevSampleGroupByFunctionFactory.java (1)

50-56: LGTM! Consistent formatting improvement.

The multi-line parameter formatting matches the pattern used in StdDevPopGroupByFunctionFactory and improves readability without changing functionality.

core/src/main/java/io/questdb/cairo/arr/ArrayView.java (3)

184-184: Good API improvement with the inverted logic.

The change from shapeEquals to shapeDiffers improves code readability by making early-exit conditions more intuitive. This is a positive refactoring that aligns with common programming patterns.


408-414: Useful accessor methods for flat view boundaries.

The addition of getHi() and getLo() provides clean access to the flat view range boundaries. This improves API usability and reduces the need for manual offset calculations in client code.


517-534: Method correctly implements shape difference logic.

The implementation properly checks for dimension count mismatch first, then iterates through dimensions to compare lengths. The inverted return values (true for differences) are correct and consistent with the method name.

core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayMaxFunctionFactoryTest.java (1)

31-133: Comprehensive test coverage for array_max function.

The test class provides excellent coverage including:

  • Edge cases (empty, null, single element)
  • Multi-dimensional arrays
  • Special double values (NaN, ±Infinity)
  • Negative values
  • Table data integration

The tests correctly verify that infinite and NaN values are ignored in the maximum calculation, which is an important behavior to document.

core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayMinFunctionFactoryTest.java (1)

31-133: Well-structured test coverage mirroring array_max tests.

The test class provides comprehensive coverage for array_min with the same scenarios as array_max, ensuring consistent behavior across both functions. The tests correctly verify that infinite and NaN values are ignored when computing the minimum.

core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMinFunctionFactory.java (2)

74-84: Efficient implementation with proper edge case handling.

The implementation correctly:

  • Returns NaN for null arrays
  • Uses a fast path for vanilla (flat) arrays
  • Handles multi-dimensional arrays via recursion
  • Filters out non-finite values (NaN, ±Infinity)
  • Returns NaN when no finite values exist

The use of Double.POSITIVE_INFINITY as the initial min value is appropriate.


113-115: Thread safety correctly documented.

The function is appropriately marked as not thread-safe due to the mutable min field used during evaluation.

core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayCountFunctionFactory.java (2)

53-78: Clean refactoring to explicit recursive implementation.

The refactoring from interface-based callbacks to explicit implementation improves code clarity. The implementation correctly:

  • Returns 0 for null arrays
  • Uses fast path for vanilla arrays via flatView.countDouble()
  • Handles multi-dimensional arrays recursively
  • Counts only finite values (excludes NaN and ±Infinity)

90-108: Well-structured recursive traversal.

The recursive method properly handles multi-dimensional arrays by:

  • Checking if at the deepest dimension before processing values
  • Using stride-based iteration for efficient traversal
  • Filtering non-finite values consistently with other array functions
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMultiplyScalarFunctionFactory.java (1)

127-142: LGTM!

The recursive traversal correctly uses flatIndex for array element access, properly handling multi-dimensional arrays.

core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMaxFunctionFactory.java (1)

59-116: LGTM!

The implementation correctly handles:

  • Null arrays returning NaN
  • Fast path for vanilla arrays using flatView().maxDouble()
  • Recursive traversal for multi-dimensional arrays
  • Proper handling of non-finite values (NaN, Infinity)
  • Thread safety correctly marked as false due to mutable state
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySumFunctionFactory.java (1)

100-124: Excellent numerical stability implementation!

The recursive calculation correctly implements Kahan summation algorithm for improved numerical accuracy when summing floating-point values. The initialization logic properly handles the transition from NaN to 0 when the first finite value is encountered.

core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySubtractScalarFunctionFactory.java (1)

56-139: LGTM!

The refactored implementation correctly:

  • Handles null arrays by returning a null array wrapper
  • Provides fast path optimization for vanilla (flat) arrays
  • Implements recursive traversal for multi-dimensional arrays with correct flatIndex usage
  • Properly manages resources with the close() method
  • Maintains consistency with other refactored array-scalar operators
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayDivScalarFunctionFactory.java (1)

123-138: Well-implemented recursive array traversal

The recursive method correctly handles multi-dimensional arrays by:

  • Traversing dimensions using stride
  • Applying the operation only at the deepest dimension
  • Maintaining the flat index throughout recursion
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayStdDevSampFunctionFactory.java (2)

96-101: Excellent use of Welford's algorithm for numerical stability

The implementation correctly uses Welford's online algorithm which provides better numerical stability compared to the naive approach of calculating variance. This prevents catastrophic cancellation when dealing with large numbers.


155-157: Correct implementation of Bessel's correction

The method properly returns count - 1 for sample variance calculation, which is the correct denominator for unbiased sample standard deviation.

core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleScalarSubtractArrayFunctionFactory.java (1)

56-141: Consistent implementation with other array operators

The refactored implementation correctly:

  • Handles null arrays by returning null
  • Processes vanilla arrays efficiently with direct iteration
  • Handles multi-dimensional arrays with recursive traversal
  • Properly manages resources with close() method
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAddScalarFunctionFactory.java (1)

56-60: Correct handling of commutative operation

The shouldSwapArgs() method correctly returns true, allowing the SQL engine to swap arguments for this commutative operation (array + scalar = scalar + array).

core/src/test/java/io/questdb/test/griffin/engine/functions/array/DoubleArrayStdDevFunctionFactoryTest.java (1)

31-496: Excellent comprehensive test coverage

The test suite thoroughly covers:

  • Edge cases (null, empty, single element arrays)
  • Multi-dimensional arrays and slices
  • Special floating-point values (NaN, Infinity)
  • Both sample and population standard deviation
  • Integration with GROUP BY aggregation
  • Numerical accuracy with precise expected values
core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayRoundFunctionFactory.java (11)

37-37: Import addition aligns with architectural refactoring.

The addition of BinaryFunction import is consistent with the refactoring where DoubleArrayRoundVarScaleFunction now implements BinaryFunction directly instead of using interface-based callbacks.


76-76: Class declaration correctly implements UnaryFunction.

The class now extends ArrayFunction and implements UnaryFunction, which is consistent with the architectural shift away from interface-based element processing to explicit function interfaces.


108-112: Simplified array preparation and processing logic.

The refactored code uses the new DirectArray.prepare() method to consolidate array setup, then processes elements with a simple loop. This is cleaner than the previous interface-based approach.


131-131: Class declaration correctly implements UnaryFunction.

Similar to the degenerate scale function, this class now properly implements UnaryFunction as part of the architectural refactoring.


165-174: Enhanced array processing with multi-dimensional support.

The refactored implementation now handles both vanilla (flat) and multi-dimensional arrays explicitly. The vanilla case uses direct flat view access, while complex arrays use recursive traversal.


192-207: Recursive traversal implementation looks correct.

The recursive method properly handles multi-dimensional arrays by:

  • Calculating stride and count for each dimension
  • Detecting the deepest dimension for actual value processing
  • Recursively traversing intermediate dimensions
  • Applying the rounding function at leaf elements

217-231: Variable scale function correctly implements BinaryFunction.

The class declaration and constructor properly handle the transition to BinaryFunction interface, maintaining both array and scalar arguments as expected for a binary operation.


234-237: Resource management correctly updated.

The close() method now calls BinaryFunction.super.close() instead of the previous interface method, which is correct for the new architecture.


240-258: Array processing logic correctly handles variable scale.

The implementation properly:

  • Retrieves the scalar value from the record
  • Uses the new DirectArray.prepare() method
  • Handles both vanilla and multi-dimensional arrays
  • Passes the scalar value to the rounding function

260-283: BinaryFunction interface methods correctly implemented.

All required BinaryFunction interface methods (getLeft(), getRight(), getName(), isOperator(), isThreadSafe()) are properly implemented with appropriate return values.


285-300: Recursive traversal for variable scale is consistent.

The recursive method implementation matches the pattern used in the positive scale function, correctly handling multi-dimensional arrays and passing the scalar value to the rounding function at leaf elements.

bluestreak01 and others added 7 commits July 31, 2025 19:43
…tddev

# Conflicts:
#	core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayAddScalarFunctionFactory.java
#	core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayDivScalarFunctionFactory.java
#	core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayMultiplyScalarFunctionFactory.java
#	core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArrayRoundFunctionFactory.java
#	core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleArraySubtractScalarFunctionFactory.java
#	core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleNegArrayFunctionFactory.java
#	core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleScalarDivArrayFunctionFactory.java
#	core/src/main/java/io/questdb/griffin/engine/functions/array/DoubleScalarSubtractArrayFunctionFactory.java
@glasstiger
Copy link
Copy Markdown
Contributor

[PR Coverage check]

😍 pass : 726 / 778 (93.32%)

file detail

path covered line new line coverage
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayRoundFunctionFactory.java 34 58 58.62%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayAddFunctionFactory.java 51 57 89.47%
🔵 io/questdb/griffin/engine/functions/array/DoubleArraySubtractFunctionFactory.java 51 57 89.47%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayDivFunctionFactory.java 53 57 92.98%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayMultiplyFunctionFactory.java 53 57 92.98%
🔵 io/questdb/griffin/engine/functions/array/DoubleScalarDivArrayFunctionFactory.java 32 33 96.97%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayCumSumFunctionFactory.java 30 31 96.77%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayAddScalarFunctionFactory.java 33 34 97.06%
🔵 io/questdb/griffin/engine/functions/array/DoubleScalarSubtractArrayFunctionFactory.java 33 34 97.06%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayDivScalarFunctionFactory.java 33 34 97.06%
🔵 io/questdb/griffin/engine/functions/array/DoubleArraySubtractScalarFunctionFactory.java 33 34 97.06%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayMultiplyScalarFunctionFactory.java 33 34 97.06%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayStdDevSampFunctionFactory.java 51 52 98.08%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayAndScalarDotProductFunctionFactory.java 24 24 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayShiftFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayMinFunctionFactory.java 28 28 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayStdDevPopFunctionFactory.java 7 7 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayCountFunctionFactory.java 18 18 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArraySumFunctionFactory.java 24 24 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayStdDevFunctionFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayShiftDefaultNaNFunctionFactory.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleNegArrayFunctionFactory.java 18 18 100.00%
🔵 io/questdb/cairo/arr/DirectArray.java 4 4 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayDotProductFunctionFactory.java 7 7 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayMaxFunctionFactory.java 28 28 100.00%
🔵 io/questdb/griffin/engine/functions/constants/ArrayConstant.java 2 2 100.00%
🔵 io/questdb/cairo/arr/ArrayView.java 6 6 100.00%
🔵 io/questdb/cairo/arr/FlatArrayView.java 12 12 100.00%
🔵 io/questdb/griffin/engine/functions/array/DoubleArrayAvgFunctionFactory.java 24 24 100.00%

@bluestreak01 bluestreak01 merged commit 0fb31e0 into master Aug 1, 2025
35 checks passed
@bluestreak01 bluestreak01 deleted the mt_array-stddev branch August 1, 2025 19:13
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.

4 participants