This is an automated email from the ASF dual-hosted git repository.

Jackie-Jiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new f7e6bb10f93 Give the covariance functions the query's null handling 
option (#19211)
f7e6bb10f93 is described below

commit f7e6bb10f9318a057b0acfb967f2964c1432cd84
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Tue Aug 11 17:48:05 2026 -0700

    Give the covariance functions the query's null handling option (#19211)
---
 .../aggregation/function/AggregationFunction.java  |   8 +-
 .../function/AggregationFunctionFactory.java       |   4 +-
 .../function/CovarianceAggregationFunction.java    |  86 ++++++++------
 .../NullableSingleInputAggregationFunction.java    |   8 +-
 .../AggregationFunctionNullContractTest.java       |   4 +-
 .../CovarianceAggregationFunctionTest.java         | 127 +++++++++++++++++++++
 .../queries/NullHandlingEnabledQueriesTest.java    |  62 ++++++++++
 7 files changed, 259 insertions(+), 40 deletions(-)

diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java
index ec1ecaaeb2e..7569aa03967 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java
@@ -100,12 +100,12 @@ import 
org.apache.pinot.segment.spi.AggregationFunctionType;
 ///      null rows and fold the column's default value into the aggregate 
whatever the query asked for: the
 ///      sketch-backed distinct counts (`DISTINCTCOUNTBITMAP`, 
`DISTINCTCOUNTHLL`, `DISTINCTCOUNTTHETASKETCH`,
 ///      `DISTINCTCOUNTCPCSKETCH`, `FASTHLL`, 
`SEGMENTPARTITIONEDDISTINCTCOUNT` and the raw and smart variants of
-///      each), the tuple and frequency sketches, the covariance functions, 
`HISTOGRAM`, `IDSET`, `STUNION`, the
-///      array sums, and the funnel family. Whether a function takes the 
option is visible at its construction site,
-///      which is the reliable way to tell.
+///      each), the tuple and frequency sketches, `HISTOGRAM`, `IDSET`, 
`STUNION`, the array sums, and the funnel
+///      family. Whether a function takes the option is visible at its 
construction site, which is the reliable way
+///      to tell.
 ///      The family names are not a safe shorthand for this, in either 
direction. The exact distinct functions
 ///      (`DISTINCTCOUNT`, `DISTINCTSUM`, `DISTINCTAVG`, 
`DISTINCTCOUNTOFFHEAP`) do take the option and skip null
-///      rows through their shared base, as do the variance and 
standard-deviation functions and the
+///      rows through their shared base, as do the variance, 
standard-deviation and covariance functions and the
 ///      first/last-with-time functions, so "the distinct-count family" and 
"the statistical functions" both include
 ///      members that honour the option and members that cannot.
 ///      These same functions also substitute an empty accumulator in 
[#extractAggregationResult],
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java
index 90c9a968afb..5616244d770 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java
@@ -458,9 +458,9 @@ public class AggregationFunctionFactory {
           case HISTOGRAM:
             return new HistogramAggregationFunction(arguments);
           case COVARPOP:
-            return new CovarianceAggregationFunction(arguments, false);
+            return new CovarianceAggregationFunction(arguments, false, 
nullHandlingEnabled);
           case COVARSAMP:
-            return new CovarianceAggregationFunction(arguments, true);
+            return new CovarianceAggregationFunction(arguments, true, 
nullHandlingEnabled);
           case BOOLAND:
             return new BooleanAndAggregationFunction(arguments, 
nullHandlingEnabled);
           case BOOLOR:
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunction.java
index 32c76d5dea0..86647cf7673 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunction.java
@@ -26,6 +26,7 @@ import javax.annotation.Nullable;
 import org.apache.pinot.common.CustomObject;
 import org.apache.pinot.common.request.context.ExpressionContext;
 import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.common.utils.RoaringBitmapUtils;
 import org.apache.pinot.core.common.BlockValSet;
 import org.apache.pinot.core.common.ObjectSerDeUtils;
 import org.apache.pinot.core.query.aggregation.AggregationResultHolder;
@@ -54,11 +55,26 @@ public class CovarianceAggregationFunction implements 
AggregationFunction<Covari
   protected final ExpressionContext _expression1;
   protected final ExpressionContext _expression2;
   protected final boolean _isSample;
+  protected final boolean _nullHandlingEnabled;
 
-  public CovarianceAggregationFunction(List<ExpressionContext> arguments, 
boolean isSample) {
+  public CovarianceAggregationFunction(List<ExpressionContext> arguments, 
boolean isSample,
+      boolean nullHandlingEnabled) {
     _expression1 = arguments.get(0);
     _expression2 = arguments.get(1);
     _isSample = isSample;
+    _nullHandlingEnabled = nullHandlingEnabled;
+  }
+
+  /// Runs `consumer` over the row ranges where **both** input columns are 
non-null.
+  ///
+  /// A covariance pairs two values per row, so a row contributes only when 
neither is null. The null positions of the
+  /// two blocks are merged as a stream rather than into a new bitmap, which 
keeps this allocation-free on the
+  /// aggregation path.
+  private void forEachNotNull(int length, BlockValSet blockValSet1, 
BlockValSet blockValSet2,
+      RoaringBitmapUtils.BatchConsumer consumer) {
+    RoaringBitmapUtils.forEachUnset(length,
+        
NullableSingleInputAggregationFunction.orNullIterator(_nullHandlingEnabled, 
blockValSet1, blockValSet2),
+        consumer);
   }
 
   @Override
@@ -98,16 +114,25 @@ public class CovarianceAggregationFunction implements 
AggregationFunction<Covari
     double[] values1 = 
StatisticalAggregationFunctionUtils.getValSet(blockValSetMap, _expression1);
     double[] values2 = 
StatisticalAggregationFunctionUtils.getValSet(blockValSetMap, _expression2);
 
-    double sumX = 0.0;
-    double sumY = 0.0;
-    double sumXY = 0.0;
+    CovarianceTuple tuple = new CovarianceTuple(0.0, 0.0, 0.0, 0L);
+    forEachNotNull(length, blockValSetMap.get(_expression1), 
blockValSetMap.get(_expression2), (from, to) -> {
+      double sumX = 0.0;
+      double sumY = 0.0;
+      double sumXY = 0.0;
+      for (int i = from; i < to; i++) {
+        sumX += values1[i];
+        sumY += values2[i];
+        sumXY += values1[i] * values2[i];
+      }
+      tuple.apply(sumX, sumY, sumXY, to - from);
+    });
 
-    for (int i = 0; i < length; i++) {
-      sumX += values1[i];
-      sumY += values2[i];
-      sumXY += values1[i] * values2[i];
+    // Leaving the holder untouched is how "nothing was aggregated" reaches 
extractFinalResult
+    if (_nullHandlingEnabled && tuple.getCount() == 0L) {
+      return;
     }
-    setAggregationResult(aggregationResultHolder, sumX, sumY, sumXY, length);
+    setAggregationResult(aggregationResultHolder, tuple.getSumX(), 
tuple.getSumY(), tuple.getSumXY(),
+        tuple.getCount());
   }
 
   protected void setAggregationResult(AggregationResultHolder 
aggregationResultHolder, double sumX, double sumY,
@@ -135,9 +160,11 @@ public class CovarianceAggregationFunction implements 
AggregationFunction<Covari
       Map<ExpressionContext, BlockValSet> blockValSetMap) {
     double[] values1 = 
StatisticalAggregationFunctionUtils.getValSet(blockValSetMap, _expression1);
     double[] values2 = 
StatisticalAggregationFunctionUtils.getValSet(blockValSetMap, _expression2);
-    for (int i = 0; i < length; i++) {
-      setGroupByResult(groupKeyArray[i], groupByResultHolder, values1[i], 
values2[i], values1[i] * values2[i], 1L);
-    }
+    forEachNotNull(length, blockValSetMap.get(_expression1), 
blockValSetMap.get(_expression2), (from, to) -> {
+      for (int i = from; i < to; i++) {
+        setGroupByResult(groupKeyArray[i], groupByResultHolder, values1[i], 
values2[i], values1[i] * values2[i], 1L);
+      }
+    });
   }
 
   @Override
@@ -145,21 +172,19 @@ public class CovarianceAggregationFunction implements 
AggregationFunction<Covari
       Map<ExpressionContext, BlockValSet> blockValSetMap) {
     double[] values1 = 
StatisticalAggregationFunctionUtils.getValSet(blockValSetMap, _expression1);
     double[] values2 = 
StatisticalAggregationFunctionUtils.getValSet(blockValSetMap, _expression2);
-    for (int i = 0; i < length; i++) {
-      for (int groupKey : groupKeysArray[i]) {
-        setGroupByResult(groupKey, groupByResultHolder, values1[i], 
values2[i], values1[i] * values2[i], 1L);
+    forEachNotNull(length, blockValSetMap.get(_expression1), 
blockValSetMap.get(_expression2), (from, to) -> {
+      for (int i = from; i < to; i++) {
+        for (int groupKey : groupKeysArray[i]) {
+          setGroupByResult(groupKey, groupByResultHolder, values1[i], 
values2[i], values1[i] * values2[i], 1L);
+        }
       }
-    }
+    });
   }
 
+  @Nullable
   @Override
   public CovarianceTuple extractAggregationResult(AggregationResultHolder 
aggregationResultHolder) {
-    CovarianceTuple covarianceTuple = aggregationResultHolder.getResult();
-    if (covarianceTuple == null) {
-      return new CovarianceTuple(0.0, 0.0, 0.0, 0L);
-    } else {
-      return covarianceTuple;
-    }
+    return aggregationResultHolder.getResult();
   }
 
   @Nullable
@@ -198,23 +223,20 @@ public class CovarianceAggregationFunction implements 
AggregationFunction<Covari
   @Nullable
   @Override
   public Double extractFinalResult(@Nullable CovarianceTuple covarianceTuple) {
-    // A null intermediate result means nothing was aggregated, and the 
covariance of nothing is NULL. A zero-count
-    // tuple means the same thing and ought to answer alike, but this function 
never receives the query's null
-    // handling option and so cannot tell the two modes apart; it keeps its 
historical sentinel below. See the first
-    // known deviation on the null contract.
-    if (covarianceTuple == null) {
-      return null;
-    }
-    long count = covarianceTuple.getCount();
+    // A null intermediate result means nothing was aggregated, and so does a 
zero count, which is what a
+    // deserialized peer can still carry. With null handling enabled the 
covariance of nothing is NULL; with it
+    // disabled it is what an untouched tuple renders to, which is the 
sentinel below.
+    long count = covarianceTuple != null ? covarianceTuple.getCount() : 0L;
     if (count == 0L) {
-      return DEFAULT_FINAL_RESULT;
+      return _nullHandlingEnabled ? null : DEFAULT_FINAL_RESULT;
     } else {
       double sumX = covarianceTuple.getSumX();
       double sumY = covarianceTuple.getSumY();
       double sumXY = covarianceTuple.getSumXY();
       if (_isSample) {
+        // A sample covariance divides by count - 1, so a single contributing 
row leaves it undefined
         if (count - 1 == 0L) {
-          return DEFAULT_FINAL_RESULT;
+          return _nullHandlingEnabled ? null : DEFAULT_FINAL_RESULT;
         }
         // sample cov = population cov * (count / (count - 1))
         return (sumXY / (count - 1)) - (sumX * sumY) / (count * (count - 1));
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/NullableSingleInputAggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/NullableSingleInputAggregationFunction.java
index 13494beaef4..e9e33604c7e 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/NullableSingleInputAggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/NullableSingleInputAggregationFunction.java
@@ -123,7 +123,13 @@ public abstract class 
NullableSingleInputAggregationFunction<I, F extends Compar
   }
 
   public IntIterator orNullIterator(BlockValSet valSet1, BlockValSet valSet2) {
-    if (!_nullHandlingEnabled) {
+    return orNullIterator(_nullHandlingEnabled, valSet1, valSet2);
+  }
+
+  /// Merges the null positions of two blocks without materializing a bitmap, 
for functions that pair a value from
+  /// each of two columns and so must skip a row when either side is null.
+  public static IntIterator orNullIterator(boolean nullHandlingEnabled, 
BlockValSet valSet1, BlockValSet valSet2) {
+    if (!nullHandlingEnabled) {
       return EmptyIntIterator.INSTANCE;
     } else {
       RoaringBitmap nullBlock1 = valSet1.getNullBitmap();
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionNullContractTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionNullContractTest.java
index 60a029c6501..e511b0e7460 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionNullContractTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionNullContractTest.java
@@ -251,7 +251,9 @@ public class AggregationFunctionNullContractTest {
       AggregationFunctionType.PERCENTILERAWKLLMV, 
AggregationFunctionType.MINSTRING, AggregationFunctionType.MAXSTRING,
       AggregationFunctionType.MINLONG, AggregationFunctionType.MAXLONG, 
AggregationFunctionType.SUMINT,
       AggregationFunctionType.SUMLONG, AggregationFunctionType.SUMPRECISION, 
AggregationFunctionType.FIRSTWITHTIME,
-      AggregationFunctionType.LASTWITHTIME, AggregationFunctionType.ARRAYAGG, 
AggregationFunctionType.LISTAGG
+      AggregationFunctionType.LASTWITHTIME, AggregationFunctionType.ARRAYAGG, 
AggregationFunctionType.LISTAGG,
+      // Given the option so they can skip null rows; a row counts only when 
both input columns are non-null
+      AggregationFunctionType.COVARPOP, AggregationFunctionType.COVARSAMP
   );
 
   /// Functions this test cannot drive with a one-column synthetic block, 
pinned so that a silent drop-out is always a
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunctionTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunctionTest.java
new file mode 100644
index 00000000000..059281f0f38
--- /dev/null
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunctionTest.java
@@ -0,0 +1,127 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.core.query.aggregation.function;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.pinot.common.request.context.ExpressionContext;
+import org.apache.pinot.core.common.BlockValSet;
+import org.apache.pinot.core.common.SyntheticBlockValSets;
+import org.apache.pinot.core.query.aggregation.AggregationResultHolder;
+import org.apache.pinot.segment.local.customobject.CovarianceTuple;
+import org.roaringbitmap.RoaringBitmap;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+
+/// Null handling for `COVAR_POP` and `COVAR_SAMP`, which pair a value from 
each of two columns per row.
+public class CovarianceAggregationFunctionTest {
+  private static final ExpressionContext X = 
ExpressionContext.forIdentifier("x");
+  private static final ExpressionContext Y = 
ExpressionContext.forIdentifier("y");
+  private static final double[] X_VALUES = {1.0, 2.0, 3.0, 4.0};
+  private static final double[] Y_VALUES = {10.0, 20.0, 30.0, 40.0};
+
+  private static CovarianceAggregationFunction create(boolean 
nullHandlingEnabled) {
+    return new CovarianceAggregationFunction(List.of(X, Y), false, 
nullHandlingEnabled);
+  }
+
+  private static Map<ExpressionContext, BlockValSet> blocks(RoaringBitmap 
nullsInX, RoaringBitmap nullsInY) {
+    return Map.of(X, SyntheticBlockValSets.Double.create(nullsInX, X_VALUES),
+        Y, SyntheticBlockValSets.Double.create(nullsInY, Y_VALUES));
+  }
+
+  private static CovarianceTuple aggregate(CovarianceAggregationFunction 
function,
+      Map<ExpressionContext, BlockValSet> blockValSetMap) {
+    AggregationResultHolder resultHolder = 
function.createAggregationResultHolder();
+    function.aggregate(X_VALUES.length, resultHolder, blockValSetMap);
+    return function.extractAggregationResult(resultHolder);
+  }
+
+  /// A row contributes only when both of its values are present, so the rows 
skipped are the union of the two
+  /// columns' nulls rather than either one alone.
+  @Test
+  public void testRowIsSkippedWhenEitherColumnIsNull() {
+    CovarianceTuple result =
+        aggregate(create(true), blocks(RoaringBitmap.bitmapOf(1), 
RoaringBitmap.bitmapOf(2)));
+
+    // Rows 1 and 2 drop out, leaving rows 0 and 3
+    assertEquals(result.getCount(), 2L);
+    assertEquals(result.getSumX(), 5.0);
+    assertEquals(result.getSumY(), 50.0);
+    assertEquals(result.getSumXY(), 170.0);
+  }
+
+  /// Both columns null in the same row, including row 0: the merged null 
stream reports the row once, and the row
+  /// is dropped once.
+  @Test
+  public void testRowNullInBothColumnsIsDroppedOnce() {
+    CovarianceTuple result =
+        aggregate(create(true), blocks(RoaringBitmap.bitmapOf(0, 2), 
RoaringBitmap.bitmapOf(0, 3)));
+
+    // Rows 0, 2 and 3 drop out, leaving row 1 alone
+    assertEquals(result.getCount(), 1L);
+    assertEquals(result.getSumX(), 2.0);
+    assertEquals(result.getSumY(), 20.0);
+    assertEquals(result.getSumXY(), 40.0);
+  }
+
+  @Test
+  public void testNothingAggregatedWhenEveryRowIsNull() {
+    RoaringBitmap allNull = new RoaringBitmap();
+    allNull.add(0L, X_VALUES.length);
+    CovarianceAggregationFunction function = create(true);
+
+    assertNull(aggregate(function, blocks(allNull, null)));
+    assertNull(function.extractFinalResult(null));
+  }
+
+  /// With the option disabled the column default is folded in, which is the 
answer this mode has always given.
+  @Test
+  public void testNullRowsFoldedInWhenOptionDisabled() {
+    RoaringBitmap allNull = new RoaringBitmap();
+    allNull.add(0L, X_VALUES.length);
+
+    CovarianceTuple result = aggregate(create(false), blocks(allNull, 
allNull));
+
+    assertEquals(result.getCount(), 4L);
+    assertEquals(result.getSumX(), 10.0);
+  }
+
+  /// `COVAR_SAMP` divides by `count - 1`, so one contributing row leaves it 
undefined rather than zero.
+  @Test
+  public void testSampleCovarianceOverASingleRowIsNull() {
+    RoaringBitmap allButFirst = RoaringBitmap.bitmapOf(1, 2, 3);
+    CovarianceAggregationFunction sample = new 
CovarianceAggregationFunction(List.of(X, Y), true, true);
+    AggregationResultHolder resultHolder = 
sample.createAggregationResultHolder();
+    sample.aggregate(X_VALUES.length, resultHolder, blocks(allButFirst, null));
+    CovarianceTuple tuple = sample.extractAggregationResult(resultHolder);
+
+    assertEquals(tuple.getCount(), 1L);
+    assertNull(sample.extractFinalResult(tuple));
+  }
+
+  /// An untouched accumulator renders the identity with the option disabled, 
and `NULL` with it enabled.
+  @Test
+  public void testEmptyInputRendersIdentityOnlyWhenOptionDisabled() {
+    assertEquals(create(false).extractFinalResult(null), 
Double.NEGATIVE_INFINITY);
+    assertNull(create(true).extractFinalResult(null));
+  }
+}
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java
index 7c2fea5720b..5a37f159b62 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java
@@ -1480,6 +1480,68 @@ public class NullHandlingEnabledQueriesTest extends 
BaseQueriesTest {
     assertEquals(rows.get(0)[0], 0.5);
   }
 
+  /// A covariance pairs a value from each column, so a row counts only when 
both are present.
+  @Test
+  public void testCovarPopSkipsRowsWhereEitherColumnIsNull()
+      throws Exception {
+    initializeRows();
+    insertRowWithTwoColumns(1, 10);
+    insertRowWithTwoColumns(null, 20);
+    insertRowWithTwoColumns(3, null);
+    insertRowWithTwoColumns(4, 40);
+    TableConfig tableConfig = new 
TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build();
+    Schema schema = new 
Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT)
+        .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build();
+    setUpSegments(tableConfig, schema);
+    String query = String.format("SELECT COVAR_POP(%s, %s) FROM testTable", 
COLUMN1, COLUMN2);
+
+    BrokerResponseNative brokerResponse = getBrokerResponse(query, 
QUERY_OPTIONS);
+
+    List<Object[]> rows = brokerResponse.getResultTable().getRows();
+    assertEquals(rows.size(), 1);
+    // Only rows 0 and 3 contribute: mean(xy) - mean(x)mean(y) = 85 - 2.5 * 25
+    assertEquals(rows.get(0)[0], 22.5);
+  }
+
+  @Test
+  public void testCovarPopOverAllNullInputIsNull()
+      throws Exception {
+    initializeRows();
+    insertRowWithTwoColumns(null, 10);
+    insertRowWithTwoColumns(null, 20);
+    TableConfig tableConfig = new 
TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build();
+    Schema schema = new 
Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT)
+        .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build();
+    setUpSegments(tableConfig, schema);
+    String query = String.format("SELECT COVAR_POP(%s, %s) FROM testTable", 
COLUMN1, COLUMN2);
+
+    BrokerResponseNative brokerResponse = getBrokerResponse(query, 
QUERY_OPTIONS);
+
+    List<Object[]> rows = brokerResponse.getResultTable().getRows();
+    assertEquals(rows.size(), 1);
+    assertNull(rows.get(0)[0]);
+  }
+
+  /// With the option off, null rows are read as the column default and folded 
in, as they always have been.
+  @Test
+  public void testCovarPopFoldsNullRowsWhenOptionDisabled()
+      throws Exception {
+    initializeRows();
+    insertRowWithTwoColumns(null, 10);
+    insertRowWithTwoColumns(null, 20);
+    TableConfig tableConfig = new 
TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build();
+    Schema schema = new 
Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT)
+        .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build();
+    setUpSegments(tableConfig, schema);
+    String query = String.format("SELECT COVAR_POP(%s, %s) FROM testTable", 
COLUMN1, COLUMN2);
+
+    BrokerResponseNative brokerResponse = getBrokerResponse(query);
+
+    List<Object[]> rows = brokerResponse.getResultTable().getRows();
+    assertEquals(rows.size(), 1);
+    assertEquals(rows.get(0)[0], 0.0);
+  }
+
   @Test(dataProvider = "NumberTypes")
   public void testGroupByStddevPop(FieldSpec.DataType dataType)
       throws Exception {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to