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]