This is an automated email from the ASF dual-hosted git repository.
jackie 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 1cb172aae7 Proper null handling in equality, inequality and membership
operators for all SV column data types (#9173)
1cb172aae7 is described below
commit 1cb172aae7f1d9cd5855c74b9f004405aeaedeaa
Author: nizarhejazi <[email protected]>
AuthorDate: Fri Aug 5 18:08:10 2022 -0700
Proper null handling in equality, inequality and membership operators for
all SV column data types (#9173)
1. Proper null handling in equality, inequality and membership operators
for all SV column data types.
2. Fix a bug in retrieving dictionary id set of the values in the given
IN/NOT_IN predicate
---
.../dociditerators/SVScanDocIdIterator.java | 259 ++++++++++++++++++++-
.../core/operator/docidsets/SVScanDocIdSet.java | 10 +-
.../core/operator/filter/FilterOperatorUtils.java | 14 +-
.../filter/RangeIndexBasedFilterOperator.java | 3 +-
.../operator/filter/ScanBasedFilterOperator.java | 7 +-
.../operator/filter/predicate/PredicateUtils.java | 20 +-
.../org/apache/pinot/core/plan/FilterPlanNode.java | 6 +-
.../core/query/reduce/GapfillFilterHandler.java | 3 +-
.../core/query/reduce/GroupByDataTableReducer.java | 3 +-
.../core/query/reduce/HavingFilterHandler.java | 5 +-
.../core/query/reduce/filter/AndRowMatcher.java | 6 +-
.../core/query/reduce/filter/NotRowMatcher.java | 5 +-
.../core/query/reduce/filter/OrRowMatcher.java | 6 +-
.../query/reduce/filter/PredicateRowMatcher.java | 7 +-
.../query/reduce/filter/RowMatcherFactory.java | 11 +-
.../core/query/reduce/HavingFilterHandlerTest.java | 8 +-
.../apache/pinot/queries/AllNullQueriesTest.java | 31 +++
.../pinot/queries/BigDecimalQueriesTest.java | 48 ++++
.../queries/BooleanNullEnabledQueriesTest.java | 194 +++++++++++++--
.../pinot/perf/BenchmarkScanDocIdIterators.java | 2 +-
20 files changed, 590 insertions(+), 58 deletions(-)
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java
index 61e0138f3a..16da5704a1 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java
@@ -18,10 +18,12 @@
*/
package org.apache.pinot.core.operator.dociditerators;
+import javax.annotation.Nullable;
import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator;
import org.apache.pinot.segment.spi.Constants;
import org.apache.pinot.segment.spi.index.reader.ForwardIndexReader;
import org.apache.pinot.segment.spi.index.reader.ForwardIndexReaderContext;
+import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader;
import org.roaringbitmap.BatchIterator;
import org.roaringbitmap.RoaringBitmapWriter;
import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
@@ -48,12 +50,17 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
private int _nextDocId = 0;
private long _numEntriesScanned = 0L;
- public SVScanDocIdIterator(PredicateEvaluator predicateEvaluator,
ForwardIndexReader reader, int numDocs) {
+ public SVScanDocIdIterator(PredicateEvaluator predicateEvaluator,
ForwardIndexReader reader, int numDocs,
+ @Nullable NullValueVectorReader nullValueReader) {
_predicateEvaluator = predicateEvaluator;
_reader = reader;
_readerContext = reader.createContext();
_numDocs = numDocs;
- _valueMatcher = getValueMatcher();
+ ImmutableRoaringBitmap nullBitmap = nullValueReader != null ?
nullValueReader.getNullBitmap() : null;
+ if (nullBitmap != null && nullBitmap.isEmpty()) {
+ nullBitmap = null;
+ }
+ _valueMatcher = getValueMatcher(nullBitmap);
}
@Override
@@ -122,25 +129,25 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
return _numEntriesScanned;
}
- private ValueMatcher getValueMatcher() {
+ private ValueMatcher getValueMatcher(@Nullable ImmutableRoaringBitmap
nullBitmap) {
if (_reader.isDictionaryEncoded()) {
- return new DictIdMatcher();
+ return nullBitmap == null ? new DictIdMatcher() : new
DictIdMatcherAndNullHandler(nullBitmap);
} else {
switch (_reader.getStoredType()) {
case INT:
- return new IntMatcher();
+ return nullBitmap == null ? new IntMatcher() : new
IntMatcherAndNullHandler(nullBitmap);
case LONG:
- return new LongMatcher();
+ return nullBitmap == null ? new LongMatcher() : new
LongMatcherAndNullHandler(nullBitmap);
case FLOAT:
- return new FloatMatcher();
+ return nullBitmap == null ? new FloatMatcher() : new
FloatMatcherAndNullHandler(nullBitmap);
case DOUBLE:
- return new DoubleMatcher();
+ return nullBitmap == null ? new DoubleMatcher() : new
DoubleMatcherAndNullHandler(nullBitmap);
case BIG_DECIMAL:
- return new BigDecimalMatcher();
+ return nullBitmap == null ? new BigDecimalMatcher() : new
BigDecimalMatcherAndNullHandler(nullBitmap);
case STRING:
- return new StringMatcher();
+ return nullBitmap == null ? new StringMatcher() : new
StringMatcherAndNullHandler(nullBitmap);
case BYTES:
- return new BytesMatcher();
+ return nullBitmap == null ? new BytesMatcher() : new
BytesMatcherAndNullHandler(nullBitmap);
default:
throw new UnsupportedOperationException();
}
@@ -172,6 +179,57 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
}
}
+ private static class MatcherUtils {
+ public static int removeNullDocs(int[] docIds, int[] values, int limit,
ImmutableRoaringBitmap nullBitmap) {
+ assert !nullBitmap.isEmpty();
+ int copyToIdx = 0;
+ for (int i = 0; i < limit; i++) {
+ if (!nullBitmap.contains(docIds[i])) {
+ // Compact non-null entries into the prefix of the docIds and values
arrays.
+ docIds[copyToIdx] = docIds[i];
+ values[copyToIdx++] = values[i];
+ }
+ }
+ return copyToIdx;
+ }
+
+ public static int removeNullDocs(int[] docIds, long[] values, int limit,
ImmutableRoaringBitmap nullBitmap) {
+ assert !nullBitmap.isEmpty();
+ int copyToIdx = 0;
+ for (int i = 0; i < limit; i++) {
+ if (!nullBitmap.contains(docIds[i])) {
+ docIds[copyToIdx] = docIds[i];
+ values[copyToIdx++] = values[i];
+ }
+ }
+ return copyToIdx;
+ }
+
+ public static int removeNullDocs(int[] docIds, float[] values, int limit,
ImmutableRoaringBitmap nullBitmap) {
+ assert !nullBitmap.isEmpty();
+ int copyToIdx = 0;
+ for (int i = 0; i < limit; i++) {
+ if (!nullBitmap.contains(docIds[i])) {
+ docIds[copyToIdx] = docIds[i];
+ values[copyToIdx++] = values[i];
+ }
+ }
+ return copyToIdx;
+ }
+
+ public static int removeNullDocs(int[] docIds, double[] values, int limit,
ImmutableRoaringBitmap nullBitmap) {
+ assert !nullBitmap.isEmpty();
+ int copyToIdx = 0;
+ for (int i = 0; i < limit; i++) {
+ if (!nullBitmap.contains(docIds[i])) {
+ docIds[copyToIdx] = docIds[i];
+ values[copyToIdx++] = values[i];
+ }
+ }
+ return copyToIdx;
+ }
+ }
+
private class DictIdMatcher implements ValueMatcher {
private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE];
@@ -188,6 +246,34 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
}
}
+ private class DictIdMatcherAndNullHandler implements ValueMatcher {
+
+ private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE];
+ private final ImmutableRoaringBitmap _nullBitmap;
+
+ public DictIdMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) {
+ _nullBitmap = nullBitmap;
+ }
+
+ @Override
+ public boolean doesValueMatch(int docId) {
+ // Any comparison (equality, inequality, or membership) with null
results in false (similar to Presto) even if
+ // the compared with value is null, and comparison is equality.
+ // To consider nulls, use: IS NULL, or IS NOT NULL operators.
+ if (_nullBitmap.contains(docId)) {
+ return false;
+ }
+ return _predicateEvaluator.applySV(_reader.getDictId(docId,
_readerContext));
+ }
+
+ @Override
+ public int matchValues(int limit, int[] docIds) {
+ _reader.readDictIds(docIds, limit, _buffer, _readerContext);
+ int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit,
_nullBitmap);
+ return _predicateEvaluator.applySV(newLimit, docIds, _buffer);
+ }
+ }
+
private class IntMatcher implements ValueMatcher {
private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE];
@@ -204,6 +290,31 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
}
}
+ private class IntMatcherAndNullHandler implements ValueMatcher {
+
+ private final ImmutableRoaringBitmap _nullBitmap;
+ private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE];
+
+ public IntMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) {
+ _nullBitmap = nullBitmap;
+ }
+
+ @Override
+ public boolean doesValueMatch(int docId) {
+ if (_nullBitmap.contains(docId)) {
+ return false;
+ }
+ return _predicateEvaluator.applySV(_reader.getInt(docId,
_readerContext));
+ }
+
+ @Override
+ public int matchValues(int limit, int[] docIds) {
+ _reader.readValuesSV(docIds, limit, _buffer, _readerContext);
+ int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit,
_nullBitmap);
+ return _predicateEvaluator.applySV(newLimit, docIds, _buffer);
+ }
+ }
+
private class LongMatcher implements ValueMatcher {
private final long[] _buffer = new long[OPTIMAL_ITERATOR_BATCH_SIZE];
@@ -220,6 +331,31 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
}
}
+ private class LongMatcherAndNullHandler implements ValueMatcher {
+
+ private final ImmutableRoaringBitmap _nullBitmap;
+ private final long[] _buffer = new long[OPTIMAL_ITERATOR_BATCH_SIZE];
+
+ public LongMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) {
+ _nullBitmap = nullBitmap;
+ }
+
+ @Override
+ public boolean doesValueMatch(int docId) {
+ if (_nullBitmap.contains(docId)) {
+ return false;
+ }
+ return _predicateEvaluator.applySV(_reader.getLong(docId,
_readerContext));
+ }
+
+ @Override
+ public int matchValues(int limit, int[] docIds) {
+ _reader.readValuesSV(docIds, limit, _buffer, _readerContext);
+ int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit,
_nullBitmap);
+ return _predicateEvaluator.applySV(newLimit, docIds, _buffer);
+ }
+ }
+
private class FloatMatcher implements ValueMatcher {
private final float[] _buffer = new float[OPTIMAL_ITERATOR_BATCH_SIZE];
@@ -236,6 +372,31 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
}
}
+ private class FloatMatcherAndNullHandler implements ValueMatcher {
+
+ private final ImmutableRoaringBitmap _nullBitmap;
+ private final float[] _buffer = new float[OPTIMAL_ITERATOR_BATCH_SIZE];
+
+ public FloatMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) {
+ _nullBitmap = nullBitmap;
+ }
+
+ @Override
+ public boolean doesValueMatch(int docId) {
+ if (_nullBitmap.contains(docId)) {
+ return false;
+ }
+ return _predicateEvaluator.applySV(_reader.getFloat(docId,
_readerContext));
+ }
+
+ @Override
+ public int matchValues(int limit, int[] docIds) {
+ _reader.readValuesSV(docIds, limit, _buffer, _readerContext);
+ int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit,
_nullBitmap);
+ return _predicateEvaluator.applySV(newLimit, docIds, _buffer);
+ }
+ }
+
private class DoubleMatcher implements ValueMatcher {
private final double[] _buffer = new double[OPTIMAL_ITERATOR_BATCH_SIZE];
@@ -252,6 +413,31 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
}
}
+ private class DoubleMatcherAndNullHandler implements ValueMatcher {
+
+ private final ImmutableRoaringBitmap _nullBitmap;
+ private final double[] _buffer = new double[OPTIMAL_ITERATOR_BATCH_SIZE];
+
+ public DoubleMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) {
+ _nullBitmap = nullBitmap;
+ }
+
+ @Override
+ public boolean doesValueMatch(int docId) {
+ if (_nullBitmap.contains(docId)) {
+ return false;
+ }
+ return _predicateEvaluator.applySV(_reader.getDouble(docId,
_readerContext));
+ }
+
+ @Override
+ public int matchValues(int limit, int[] docIds) {
+ _reader.readValuesSV(docIds, limit, _buffer, _readerContext);
+ int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit,
_nullBitmap);
+ return _predicateEvaluator.applySV(newLimit, docIds, _buffer);
+ }
+ }
+
private class BigDecimalMatcher implements ValueMatcher {
@Override
@@ -260,6 +446,23 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
}
}
+ private class BigDecimalMatcherAndNullHandler implements ValueMatcher {
+
+ private final ImmutableRoaringBitmap _nullBitmap;
+
+ public BigDecimalMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) {
+ _nullBitmap = nullBitmap;
+ }
+
+ @Override
+ public boolean doesValueMatch(int docId) {
+ if (_nullBitmap.contains(docId)) {
+ return false;
+ }
+ return _predicateEvaluator.applySV(_reader.getBigDecimal(docId,
_readerContext));
+ }
+ }
+
private class StringMatcher implements ValueMatcher {
@Override
@@ -268,6 +471,23 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
}
}
+ private class StringMatcherAndNullHandler implements ValueMatcher {
+
+ private final ImmutableRoaringBitmap _nullBitmap;
+
+ public StringMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) {
+ _nullBitmap = nullBitmap;
+ }
+
+ @Override
+ public boolean doesValueMatch(int docId) {
+ if (_nullBitmap.contains(docId)) {
+ return false;
+ }
+ return _predicateEvaluator.applySV(_reader.getString(docId,
_readerContext));
+ }
+ }
+
private class BytesMatcher implements ValueMatcher {
@Override
@@ -275,4 +495,21 @@ public final class SVScanDocIdIterator implements
ScanBasedDocIdIterator {
return _predicateEvaluator.applySV(_reader.getBytes(docId,
_readerContext));
}
}
+
+ private class BytesMatcherAndNullHandler implements ValueMatcher {
+
+ private final ImmutableRoaringBitmap _nullBitmap;
+
+ public BytesMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) {
+ _nullBitmap = nullBitmap;
+ }
+
+ @Override
+ public boolean doesValueMatch(int docId) {
+ if (_nullBitmap.contains(docId)) {
+ return false;
+ }
+ return _predicateEvaluator.applySV(_reader.getBytes(docId,
_readerContext));
+ }
+ }
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java
index 919d6525bb..e55998ca40 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java
@@ -20,14 +20,18 @@ package org.apache.pinot.core.operator.docidsets;
import org.apache.pinot.core.operator.dociditerators.SVScanDocIdIterator;
import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator;
-import org.apache.pinot.segment.spi.index.reader.ForwardIndexReader;
+import org.apache.pinot.segment.spi.datasource.DataSource;
+import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader;
public final class SVScanDocIdSet implements FilterBlockDocIdSet {
private final SVScanDocIdIterator _docIdIterator;
- public SVScanDocIdSet(PredicateEvaluator predicateEvaluator,
ForwardIndexReader<?> reader, int numDocs) {
- _docIdIterator = new SVScanDocIdIterator(predicateEvaluator, reader,
numDocs);
+ public SVScanDocIdSet(PredicateEvaluator predicateEvaluator, DataSource
dataSource, int numDocs,
+ boolean nullHandlingEnabled) {
+ NullValueVectorReader nullValueVector = nullHandlingEnabled ?
dataSource.getNullValueVector() : null;
+ _docIdIterator = new SVScanDocIdIterator(
+ predicateEvaluator, dataSource.getForwardIndex(), numDocs,
nullValueVector);
}
@Override
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java
index b0c4a13856..3aa1dfafcd 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java
@@ -36,6 +36,14 @@ public class FilterOperatorUtils {
*/
public static BaseFilterOperator getLeafFilterOperator(PredicateEvaluator
predicateEvaluator, DataSource dataSource,
int numDocs) {
+ return getLeafFilterOperator(predicateEvaluator, dataSource, numDocs,
false);
+ }
+
+ /**
+ * Returns the leaf filter operator (i.e. not {@link AndFilterOperator} or
{@link OrFilterOperator}).
+ */
+ public static BaseFilterOperator getLeafFilterOperator(PredicateEvaluator
predicateEvaluator, DataSource dataSource,
+ int numDocs, boolean nullHandlingEnabled) {
if (predicateEvaluator.isAlwaysFalse()) {
return EmptyFilterOperator.getInstance();
} else if (predicateEvaluator.isAlwaysTrue()) {
@@ -56,7 +64,7 @@ public class FilterOperatorUtils {
if (dataSource.getRangeIndex() != null) {
return new RangeIndexBasedFilterOperator(predicateEvaluator,
dataSource, numDocs);
}
- return new ScanBasedFilterOperator(predicateEvaluator, dataSource,
numDocs);
+ return new ScanBasedFilterOperator(predicateEvaluator, dataSource,
numDocs, nullHandlingEnabled);
} else if (predicateType == Predicate.Type.REGEXP_LIKE) {
if (dataSource.getFSTIndex() != null &&
dataSource.getDataSourceMetadata().isSorted()) {
return new SortedIndexBasedFilterOperator(predicateEvaluator,
dataSource, numDocs);
@@ -64,7 +72,7 @@ public class FilterOperatorUtils {
if (dataSource.getFSTIndex() != null && dataSource.getInvertedIndex() !=
null) {
return new BitmapBasedFilterOperator(predicateEvaluator, dataSource,
numDocs);
}
- return new ScanBasedFilterOperator(predicateEvaluator, dataSource,
numDocs);
+ return new ScanBasedFilterOperator(predicateEvaluator, dataSource,
numDocs, nullHandlingEnabled);
} else {
if (dataSource.getDataSourceMetadata().isSorted() &&
dataSource.getDictionary() != null) {
return new SortedIndexBasedFilterOperator(predicateEvaluator,
dataSource, numDocs);
@@ -72,7 +80,7 @@ public class FilterOperatorUtils {
if (dataSource.getInvertedIndex() != null) {
return new BitmapBasedFilterOperator(predicateEvaluator, dataSource,
numDocs);
}
- return new ScanBasedFilterOperator(predicateEvaluator, dataSource,
numDocs);
+ return new ScanBasedFilterOperator(predicateEvaluator, dataSource,
numDocs, nullHandlingEnabled);
}
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java
index 66f6ab667d..4b968ba2ed 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java
@@ -78,9 +78,10 @@ public class RangeIndexBasedFilterOperator extends
BaseFilterOperator {
if (partialMatches == null) {
return new FilterBlock(new BitmapDocIdSet(matches == null ? new
MutableRoaringBitmap() : matches, _numDocs));
}
+ // TODO: support proper null handling in range index.
// Need to scan the first and last range as they might be partially matched
ScanBasedFilterOperator scanBasedFilterOperator =
- new ScanBasedFilterOperator(_rangePredicateEvaluator, _dataSource,
_numDocs);
+ new ScanBasedFilterOperator(_rangePredicateEvaluator, _dataSource,
_numDocs, false);
FilterBlockDocIdSet scanBasedDocIdSet =
scanBasedFilterOperator.getNextBlock().getBlockDocIdSet();
MutableRoaringBitmap docIds = ((ScanBasedDocIdIterator)
scanBasedDocIdSet.iterator()).applyAnd(partialMatches);
if (matches != null) {
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java
index ec7c0e2a8c..ac08c9337c 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java
@@ -35,18 +35,21 @@ public class ScanBasedFilterOperator extends
BaseFilterOperator {
private final PredicateEvaluator _predicateEvaluator;
private final DataSource _dataSource;
private final int _numDocs;
+ private final boolean _nullHandlingEnabled;
- ScanBasedFilterOperator(PredicateEvaluator predicateEvaluator, DataSource
dataSource, int numDocs) {
+ ScanBasedFilterOperator(PredicateEvaluator predicateEvaluator, DataSource
dataSource, int numDocs,
+ boolean nullHandlingEnabled) {
_predicateEvaluator = predicateEvaluator;
_dataSource = dataSource;
_numDocs = numDocs;
+ _nullHandlingEnabled = nullHandlingEnabled;
}
@Override
protected FilterBlock getNextBlock() {
DataSourceMetadata dataSourceMetadata =
_dataSource.getDataSourceMetadata();
if (dataSourceMetadata.isSingleValue()) {
- return new FilterBlock(new SVScanDocIdSet(_predicateEvaluator,
_dataSource.getForwardIndex(), _numDocs));
+ return new FilterBlock(new SVScanDocIdSet(_predicateEvaluator,
_dataSource, _numDocs, _nullHandlingEnabled));
} else {
return new FilterBlock(new MVScanDocIdSet(_predicateEvaluator,
_dataSource.getForwardIndex(), _numDocs,
dataSourceMetadata.getMaxNumValuesPerMVEntry()));
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java
index 19ca4c1176..a487d36654 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java
@@ -74,7 +74,7 @@ public class PredicateUtils {
List<String> values = inPredicate.getValues();
int hashSetSize = Integer.min(HashUtil.getMinHashSetSize(values.size()),
MAX_INITIAL_DICT_ID_SET_SIZE);
IntSet dictIdSet = new IntOpenHashSet(hashSetSize);
- switch (dataType.getStoredType()) {
+ switch (dataType) {
case INT:
int[] intValues = inPredicate.getIntValues();
for (int value : intValues) {
@@ -120,6 +120,24 @@ public class PredicateUtils {
}
}
break;
+ case BOOLEAN:
+ int[] booleanValues = inPredicate.getBooleanValues();
+ for (int value : booleanValues) {
+ int dictId = dictionary.indexOf(value);
+ if (dictId >= 0) {
+ dictIdSet.add(dictId);
+ }
+ }
+ break;
+ case TIMESTAMP:
+ long[] timestampValues = inPredicate.getTimestampValues();
+ for (long value : timestampValues) {
+ int dictId = dictionary.indexOf(value);
+ if (dictId >= 0) {
+ dictIdSet.add(dictId);
+ }
+ }
+ break;
case STRING:
for (String value : values) {
int dictId = dictionary.indexOf(value);
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/plan/FilterPlanNode.java
b/pinot-core/src/main/java/org/apache/pinot/core/plan/FilterPlanNode.java
index 6e24267bf9..b04ae67e72 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/plan/FilterPlanNode.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/FilterPlanNode.java
@@ -275,7 +275,8 @@ public class FilterPlanNode implements PlanNode {
dataSource.getDataSourceMetadata().getDataType());
}
_predicateEvaluators.add(Pair.of(predicate, predicateEvaluator));
- return
FilterOperatorUtils.getLeafFilterOperator(predicateEvaluator, dataSource,
numDocs);
+ return
FilterOperatorUtils.getLeafFilterOperator(predicateEvaluator, dataSource,
numDocs,
+ _queryContext.isNullHandlingEnabled());
case JSON_MATCH:
JsonIndexReader jsonIndex = dataSource.getJsonIndex();
Preconditions.checkState(jsonIndex != null, "Cannot apply
JSON_MATCH on column: %s without json index",
@@ -300,7 +301,8 @@ public class FilterPlanNode implements PlanNode {
PredicateEvaluatorProvider.getPredicateEvaluator(predicate,
dataSource.getDictionary(),
dataSource.getDataSourceMetadata().getDataType());
_predicateEvaluators.add(Pair.of(predicate, predicateEvaluator));
- return
FilterOperatorUtils.getLeafFilterOperator(predicateEvaluator, dataSource,
numDocs);
+ return
FilterOperatorUtils.getLeafFilterOperator(predicateEvaluator, dataSource,
numDocs,
+ _queryContext.isNullHandlingEnabled());
}
}
default:
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillFilterHandler.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillFilterHandler.java
index 919f99e7bd..8ec110a0cf 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillFilterHandler.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillFilterHandler.java
@@ -48,7 +48,8 @@ public class GapfillFilterHandler implements
ValueExtractorFactory {
// TODO: Please refer to {@link PostAggregationHandler} on how to handle
the index for aggregation queries.
_indexes.put(_dataSchema.getColumnName(i), i);
}
- _rowMatcher = RowMatcherFactory.getRowMatcher(filter, this);
+ // TODO: support proper null handling in GapfillFilterHandler.
+ _rowMatcher = RowMatcherFactory.getRowMatcher(filter, this, false);
}
/**
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
index 45144556fe..f167ca8a50 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
@@ -148,7 +148,8 @@ public class GroupByDataTableReducer implements
DataTableReducer {
FilterContext havingFilter = _queryContext.getHavingFilter();
if (havingFilter != null) {
rows = new ArrayList<>();
- HavingFilterHandler havingFilterHandler = new
HavingFilterHandler(havingFilter, postAggregationHandler);
+ HavingFilterHandler havingFilterHandler = new
HavingFilterHandler(havingFilter, postAggregationHandler,
+ _queryContext.isNullHandlingEnabled());
while (rows.size() < limit && sortedIterator.hasNext()) {
Object[] row = sortedIterator.next().getValues();
extractFinalAggregationResults(row);
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/HavingFilterHandler.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/HavingFilterHandler.java
index 885a8f16d7..07d30ebc3e 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/HavingFilterHandler.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/HavingFilterHandler.java
@@ -29,8 +29,9 @@ import
org.apache.pinot.core.query.reduce.filter.RowMatcherFactory;
public class HavingFilterHandler {
private final RowMatcher _rowMatcher;
- public HavingFilterHandler(FilterContext havingFilter,
PostAggregationHandler postAggregationHandler) {
- _rowMatcher = RowMatcherFactory.getRowMatcher(havingFilter,
postAggregationHandler);
+ public HavingFilterHandler(FilterContext havingFilter,
PostAggregationHandler postAggregationHandler,
+ boolean nullHandlingEnabled) {
+ _rowMatcher = RowMatcherFactory.getRowMatcher(havingFilter,
postAggregationHandler, nullHandlingEnabled);
}
/**
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/AndRowMatcher.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/AndRowMatcher.java
index 0991952748..3913e60e38 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/AndRowMatcher.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/AndRowMatcher.java
@@ -28,11 +28,13 @@ import
org.apache.pinot.common.request.context.FilterContext;
public class AndRowMatcher implements RowMatcher {
private final RowMatcher[] _childMatchers;
- public AndRowMatcher(List<FilterContext> childFilters, ValueExtractorFactory
valueExtractorFactory) {
+ public AndRowMatcher(List<FilterContext> childFilters, ValueExtractorFactory
valueExtractorFactory,
+ boolean nullHandlingEnabled) {
int numChildren = childFilters.size();
_childMatchers = new RowMatcher[numChildren];
for (int i = 0; i < numChildren; i++) {
- _childMatchers[i] = RowMatcherFactory.getRowMatcher(childFilters.get(i),
valueExtractorFactory);
+ _childMatchers[i] = RowMatcherFactory.getRowMatcher(childFilters.get(i),
valueExtractorFactory,
+ nullHandlingEnabled);
}
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/NotRowMatcher.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/NotRowMatcher.java
index 4b6b51d64f..c594a74780 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/NotRowMatcher.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/NotRowMatcher.java
@@ -27,8 +27,9 @@ import org.apache.pinot.common.request.context.FilterContext;
public class NotRowMatcher implements RowMatcher {
private final RowMatcher _childMatcher;
- public NotRowMatcher(FilterContext childFilter, ValueExtractorFactory
valueExtractorFactory) {
- _childMatcher = RowMatcherFactory.getRowMatcher(childFilter,
valueExtractorFactory);
+ public NotRowMatcher(FilterContext childFilter, ValueExtractorFactory
valueExtractorFactory,
+ boolean nullHandlingEnabled) {
+ _childMatcher = RowMatcherFactory.getRowMatcher(childFilter,
valueExtractorFactory, nullHandlingEnabled);
}
@Override
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/OrRowMatcher.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/OrRowMatcher.java
index 543d518271..0b1ae88bbc 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/OrRowMatcher.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/OrRowMatcher.java
@@ -28,11 +28,13 @@ import
org.apache.pinot.common.request.context.FilterContext;
public class OrRowMatcher implements RowMatcher {
private final RowMatcher[] _childMatchers;
- public OrRowMatcher(List<FilterContext> childFilters, ValueExtractorFactory
valueExtractorFactory) {
+ public OrRowMatcher(List<FilterContext> childFilters, ValueExtractorFactory
valueExtractorFactory,
+ boolean nullHandlingEnabled) {
int numChildren = childFilters.size();
_childMatchers = new RowMatcher[numChildren];
for (int i = 0; i < numChildren; i++) {
- _childMatchers[i] = RowMatcherFactory.getRowMatcher(childFilters.get(i),
valueExtractorFactory);
+ _childMatchers[i] = RowMatcherFactory.getRowMatcher(childFilters.get(i),
valueExtractorFactory,
+ nullHandlingEnabled);
}
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/PredicateRowMatcher.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/PredicateRowMatcher.java
index d2eddb8d17..edd5c6caeb 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/PredicateRowMatcher.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/PredicateRowMatcher.java
@@ -33,16 +33,21 @@ public class PredicateRowMatcher implements RowMatcher {
private final ValueExtractor _valueExtractor;
private final DataType _valueType;
private final PredicateEvaluator _predicateEvaluator;
+ private final boolean _nullHandlingEnabled;
- public PredicateRowMatcher(Predicate predicate, ValueExtractor
valueExtractor) {
+ public PredicateRowMatcher(Predicate predicate, ValueExtractor
valueExtractor, boolean nullHandlingEnabled) {
_valueExtractor = valueExtractor;
_valueType = _valueExtractor.getColumnDataType().toDataType();
_predicateEvaluator =
PredicateEvaluatorProvider.getPredicateEvaluator(predicate, null, _valueType);
+ _nullHandlingEnabled = nullHandlingEnabled;
}
@Override
public boolean isMatch(Object[] row) {
Object value = _valueExtractor.extract(row);
+ if (_nullHandlingEnabled && value == null) {
+ return false;
+ }
switch (_valueType) {
case INT:
return _predicateEvaluator.applySV((int) value);
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/RowMatcherFactory.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/RowMatcherFactory.java
index d7f04213d2..575b3f4575 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/RowMatcherFactory.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/RowMatcherFactory.java
@@ -31,18 +31,19 @@ public class RowMatcherFactory {
/**
* Helper method to construct a RowMatcher based on the given filter.
*/
- public static RowMatcher getRowMatcher(FilterContext filter,
ValueExtractorFactory valueExtractorFactory) {
+ public static RowMatcher getRowMatcher(FilterContext filter,
ValueExtractorFactory valueExtractorFactory,
+ boolean nullHandlingEnabled) {
switch (filter.getType()) {
case AND:
- return new AndRowMatcher(filter.getChildren(), valueExtractorFactory);
+ return new AndRowMatcher(filter.getChildren(), valueExtractorFactory,
nullHandlingEnabled);
case OR:
- return new OrRowMatcher(filter.getChildren(), valueExtractorFactory);
+ return new OrRowMatcher(filter.getChildren(), valueExtractorFactory,
nullHandlingEnabled);
case NOT:
assert filter.getChildren().size() == 1;
- return new NotRowMatcher(filter.getChildren().get(0),
valueExtractorFactory);
+ return new NotRowMatcher(filter.getChildren().get(0),
valueExtractorFactory, nullHandlingEnabled);
case PREDICATE:
return new PredicateRowMatcher(filter.getPredicate(),
-
valueExtractorFactory.getValueExtractor(filter.getPredicate().getLhs()));
+
valueExtractorFactory.getValueExtractor(filter.getPredicate().getLhs()),
nullHandlingEnabled);
default:
throw new IllegalStateException();
}
diff --git
a/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/HavingFilterHandlerTest.java
b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/HavingFilterHandlerTest.java
index d75db62a7d..6c9b47b7cc 100644
---
a/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/HavingFilterHandlerTest.java
+++
b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/HavingFilterHandlerTest.java
@@ -40,7 +40,7 @@ public class HavingFilterHandlerTest {
new DataSchema(new String[]{"d1", "count(*)"}, new
ColumnDataType[]{ColumnDataType.INT, ColumnDataType.LONG});
PostAggregationHandler postAggregationHandler = new
PostAggregationHandler(queryContext, dataSchema);
HavingFilterHandler havingFilterHandler =
- new HavingFilterHandler(queryContext.getHavingFilter(),
postAggregationHandler);
+ new HavingFilterHandler(queryContext.getHavingFilter(),
postAggregationHandler, false);
assertFalse(havingFilterHandler.isMatch(new Object[]{1, 5L}));
assertTrue(havingFilterHandler.isMatch(new Object[]{2, 10L}));
assertFalse(havingFilterHandler.isMatch(new Object[]{3, 3L}));
@@ -55,7 +55,7 @@ public class HavingFilterHandlerTest {
new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.DOUBLE,
ColumnDataType.DOUBLE});
PostAggregationHandler postAggregationHandler = new
PostAggregationHandler(queryContext, dataSchema);
HavingFilterHandler havingFilterHandler =
- new HavingFilterHandler(queryContext.getHavingFilter(),
postAggregationHandler);
+ new HavingFilterHandler(queryContext.getHavingFilter(),
postAggregationHandler, false);
assertFalse(havingFilterHandler.isMatch(new Object[]{1, 15.5, 13.0}));
assertTrue(havingFilterHandler.isMatch(new Object[]{2, 15.0, 3.0}));
assertFalse(havingFilterHandler.isMatch(new Object[]{3, 20.0, 7.5}));
@@ -69,7 +69,7 @@ public class HavingFilterHandlerTest {
new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.DOUBLE,
ColumnDataType.DOUBLE});
PostAggregationHandler postAggregationHandler = new
PostAggregationHandler(queryContext, dataSchema);
HavingFilterHandler havingFilterHandler =
- new HavingFilterHandler(queryContext.getHavingFilter(),
postAggregationHandler);
+ new HavingFilterHandler(queryContext.getHavingFilter(),
postAggregationHandler, false);
assertFalse(havingFilterHandler.isMatch(new Object[]{1, 15.5, 13.0}));
assertTrue(havingFilterHandler.isMatch(new Object[]{2, 15.0, 3.0}));
assertFalse(havingFilterHandler.isMatch(new Object[]{3, 20.0, 10.0}));
@@ -87,7 +87,7 @@ public class HavingFilterHandlerTest {
});
PostAggregationHandler postAggregationHandler = new
PostAggregationHandler(queryContext, dataSchema);
HavingFilterHandler havingFilterHandler =
- new HavingFilterHandler(queryContext.getHavingFilter(),
postAggregationHandler);
+ new HavingFilterHandler(queryContext.getHavingFilter(),
postAggregationHandler, false);
assertTrue(havingFilterHandler.isMatch(new Object[]{11, 11L, 10.5f,
10.5, "11", new byte[]{17}, 5}));
assertFalse(havingFilterHandler.isMatch(new Object[]{10, 11L, 10.5f,
10.5, "11", new byte[]{17}, 5}));
assertFalse(havingFilterHandler.isMatch(new Object[]{11, 10L, 10.5f,
10.5, "11", new byte[]{17}, 5}));
diff --git
a/pinot-core/src/test/java/org/apache/pinot/queries/AllNullQueriesTest.java
b/pinot-core/src/test/java/org/apache/pinot/queries/AllNullQueriesTest.java
index 305ed783a5..ee4274cc53 100644
--- a/pinot-core/src/test/java/org/apache/pinot/queries/AllNullQueriesTest.java
+++ b/pinot-core/src/test/java/org/apache/pinot/queries/AllNullQueriesTest.java
@@ -301,6 +301,17 @@ public class AllNullQueriesTest extends BaseQueriesTest {
assertNull(row[0]);
}
}
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s is not
null limit 5000",
+ COLUMN_NAME, COLUMN_NAME);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{COLUMN_NAME}, new
ColumnDataType[]{columnDataType}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), 0);
+ }
if (columnDataType != ColumnDataType.STRING) {
{
String query = String.format(
@@ -495,6 +506,26 @@ public class AllNullQueriesTest extends BaseQueriesTest {
assertEquals(rows.size(), 0);
}
if (columnDataType != ColumnDataType.STRING) {
+ {
+ String query = String.format("SELECT COUNT(%s) AS count, MIN(%s) AS
min, MAX(%s) AS max, SUM(%s) AS sum" + " "
+ + "FROM testTable GROUP BY %s ORDER BY max", COLUMN_NAME,
COLUMN_NAME, COLUMN_NAME, COLUMN_NAME,
+ COLUMN_NAME);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema, new DataSchema(new String[]{"count", "min",
"max", "sum"}, new ColumnDataType[]{
+ ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE,
ColumnDataType.DOUBLE
+ }));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), 1);
+ Object[] row = rows.get(0);
+ assertEquals(row.length, 4);
+ // Count(column) return 0 if all values are nulls.
+ assertEquals(row[0], 0L);
+ assertNull(row[1]);
+ assertNull(row[2]);
+ assertNull(row[3]);
+ }
{
String query = String.format(
"SELECT AVG(%s) AS avg FROM testTable GROUP BY %s ORDER BY avg
LIMIT 20", COLUMN_NAME, COLUMN_NAME);
diff --git
a/pinot-core/src/test/java/org/apache/pinot/queries/BigDecimalQueriesTest.java
b/pinot-core/src/test/java/org/apache/pinot/queries/BigDecimalQueriesTest.java
index a600a4d8e6..05c2ed7480 100644
---
a/pinot-core/src/test/java/org/apache/pinot/queries/BigDecimalQueriesTest.java
+++
b/pinot-core/src/test/java/org/apache/pinot/queries/BigDecimalQueriesTest.java
@@ -401,6 +401,54 @@ public class BigDecimalQueriesTest extends BaseQueriesTest
{
// List<Object[]> rows = resultTable.getRows();
// assertEquals(rows.size(), 1);
}
+ {
+ String query = String.format(
+ "SELECT MAX(%s) AS maxValue FROM testTable GROUP BY %s HAVING
maxValue < %s ORDER BY maxValue",
+ BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN,
BASE_BIG_DECIMAL.add(BigDecimal.valueOf(5)));
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{"maxValue"}, new
ColumnDataType[]{ColumnDataType.DOUBLE}));
+ List<Object[]> rows = resultTable.getRows();
+ // The default null ordering is: 'NULLS LAST'. This is why the number of
returned value is 4 and not 5.
+ assertEquals(rows.size(), 4);
+ int i = 0;
+ for (int index = 0; index < 4; index++) {
+ if (i % 4 == 3) {
+ // Null values are inserted at: index % 4 == 3.
+ i++;
+ }
+ Object[] row = rows.get(index);
+ assertEquals(row.length, 1);
+ assertEquals(row[0],
BASE_BIG_DECIMAL.add(BigDecimal.valueOf(i)).doubleValue());
+ i++;
+ }
+ }
+ {
+ int lowerLimit = 991;
+ String query = String.format(
+ "SELECT MAX(%s) AS maxValue FROM testTable GROUP BY %s HAVING
maxValue > %s ORDER BY maxValue",
+ BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN,
BASE_BIG_DECIMAL.add(BigDecimal.valueOf(lowerLimit)));
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{"maxValue"}, new
ColumnDataType[]{ColumnDataType.DOUBLE}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), 6);
+ int i = lowerLimit;
+ for (int index = 0; index < 6; index++) {
+ if (i % 4 == 3) {
+ // Null values are inserted at: index % 4 == 3.
+ i++;
+ }
+ Object[] row = rows.get(index);
+ assertEquals(row.length, 1);
+ assertEquals(row[0],
BASE_BIG_DECIMAL.add(BigDecimal.valueOf(i)).doubleValue());
+ i++;
+ }
+ }
DataTableFactory.setDataTableVersion(DataTableFactory.VERSION_3);
}
diff --git
a/pinot-core/src/test/java/org/apache/pinot/queries/BooleanNullEnabledQueriesTest.java
b/pinot-core/src/test/java/org/apache/pinot/queries/BooleanNullEnabledQueriesTest.java
index 8f080ecc92..4c3fdd40ba 100644
---
a/pinot-core/src/test/java/org/apache/pinot/queries/BooleanNullEnabledQueriesTest.java
+++
b/pinot-core/src/test/java/org/apache/pinot/queries/BooleanNullEnabledQueriesTest.java
@@ -73,6 +73,10 @@ public class BooleanNullEnabledQueriesTest extends
BaseQueriesTest {
private IndexSegment _indexSegment;
private List<IndexSegment> _indexSegments;
+ private int _trueValuesCount;
+ private int _falseValuesCount;
+ private int _nullValuesCount;
+
@Override
protected String getFilter() {
return "";
@@ -100,24 +104,31 @@ public class BooleanNullEnabledQueriesTest extends
BaseQueriesTest {
switch (i % 7) {
case 0:
record.putValue(BOOLEAN_COLUMN, false);
+ _falseValuesCount++;
break;
case 1:
record.putValue(BOOLEAN_COLUMN, 1);
+ _trueValuesCount++;
break;
case 2:
record.putValue(BOOLEAN_COLUMN, 0L);
+ _falseValuesCount++;
break;
case 3:
record.putValue(BOOLEAN_COLUMN, 0.1f);
+ _trueValuesCount++;
break;
case 4:
record.putValue(BOOLEAN_COLUMN, 0.0);
+ _falseValuesCount++;
break;
case 5:
record.putValue(BOOLEAN_COLUMN, "true");
+ _trueValuesCount++;
break;
case 6:
record.putValue(BOOLEAN_COLUMN, null);
+ _nullValuesCount++;
break;
default:
break;
@@ -173,6 +184,145 @@ public class BooleanNullEnabledQueriesTest extends
BaseQueriesTest {
Map<String, String> queryOptions = new HashMap<>();
queryOptions.put("enableNullHandling", "true");
HashSet<Integer> trueIndices = new HashSet<Integer>(Arrays.asList(1, 3,
5));
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s is null
LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), _nullValuesCount * 4);
+ for (Object[] row : rows) {
+ assertNull(row[0]);
+ }
+ }
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s = false
LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), _falseValuesCount * 4);
+ for (Object[] row : rows) {
+ assertFalse((boolean) row[0]);
+ }
+ }
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s != false
LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), _trueValuesCount * 4);
+ for (Object[] row : rows) {
+ assertTrue((boolean) row[0]);
+ }
+ }
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s = true
LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), _trueValuesCount * 4);
+ for (Object[] row : rows) {
+ assertTrue((boolean) row[0]);
+ }
+ }
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s in
(true) LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), _trueValuesCount * 4);
+ for (Object[] row : rows) {
+ assertTrue((boolean) row[0]);
+ }
+ }
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s not in
(true) LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), _falseValuesCount * 4);
+ for (Object[] row : rows) {
+ assertFalse((boolean) row[0]);
+ }
+ }
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s in (1)
LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), _trueValuesCount * 4);
+ for (Object[] row : rows) {
+ assertTrue((boolean) row[0]);
+ }
+ }
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s in
(false) LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), _falseValuesCount * 4);
+ for (Object[] row : rows) {
+ assertFalse((boolean) row[0]);
+ }
+ }
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s != true
LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ // Note: comparison w/ nulls always return fasle including inequality.
To be able to return rows w/ both nulls
+ // and false values, we should introduce IS DISTINCT FROM, and IS NOT
DISTINCT FROM operators.
+ assertEquals(rows.size(), _falseValuesCount * 4);
+ for (Object[] row : rows) {
+ assertFalse((boolean) row[0]);
+ }
+ }
+ {
+ String query = String.format("SELECT %s FROM testTable WHERE %s is not
null LIMIT 5000",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), _trueValuesCount * 4 + _falseValuesCount * 4);
+ }
{
String query = "SELECT * FROM testTable";
BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
@@ -194,26 +344,41 @@ public class BooleanNullEnabledQueriesTest extends
BaseQueriesTest {
}
}
}
+ {
+ String query = "SELECT booleanColumn FROM testTable WHERE
booleanColumn";
+ BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
+ ResultTable resultTable = brokerResponse.getResultTable();
+ DataSchema dataSchema = resultTable.getDataSchema();
+ assertEquals(dataSchema,
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ List<Object[]> rows = resultTable.getRows();
+ assertEquals(rows.size(), 10);
+ for (int i = 0; i < 10; i++) {
+ Object[] row = rows.get(i);
+ assertEquals(row.length, 1);
+ assertEquals(row[0], true);
+ }
+ }
{
String query = "SELECT * FROM testTable ORDER BY booleanColumn DESC
LIMIT 4000";
BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
ResultTable resultTable = brokerResponse.getResultTable();
DataSchema dataSchema = resultTable.getDataSchema();
assertEquals(dataSchema,
- new DataSchema(new String[]{"booleanColumn"}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
List<Object[]> rows = resultTable.getRows();
assertEquals(rows.size(), 4000);
- for (int i = 0; i < 1716; i++) {
+ for (int i = 0; i < _trueValuesCount * 4; i++) {
Object[] row = rows.get(i);
assertEquals(row.length, 1);
assertTrue((boolean) row[0]);
}
- for (int i = 1716; i < 3432; i++) {
+ for (int i = _trueValuesCount * 4; i < _trueValuesCount * 4 +
_falseValuesCount * 4; i++) {
Object[] row = rows.get(i);
assertEquals(row.length, 1);
assertFalse((boolean) row[0]);
}
- for (int i = 3432; i < 4000; i++) {
+ for (int i = _trueValuesCount * 4 + _falseValuesCount * 4; i < 4000;
i++) {
Object[] row = rows.get(i);
assertEquals(row.length, 1);
// Note 2: The default null ordering is 'NULLS LAST', regardless of
the ordering direction.
@@ -242,12 +407,13 @@ public class BooleanNullEnabledQueriesTest extends
BaseQueriesTest {
}
}
{
- String query = "SELECT DISTINCT booleanColumn FROM testTable ORDER BY
booleanColumn DESC";
+ String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY
%s DESC",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN);
BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
ResultTable resultTable = brokerResponse.getResultTable();
DataSchema dataSchema = resultTable.getDataSchema();
assertEquals(dataSchema,
- new DataSchema(new String[]{"booleanColumn"}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
+ new DataSchema(new String[]{BOOLEAN_COLUMN}, new
ColumnDataType[]{ColumnDataType.BOOLEAN}));
List<Object[]> rows = resultTable.getRows();
assertEquals(rows.size(), 3);
Object[] firstRow = rows.get(0);
@@ -261,26 +427,26 @@ public class BooleanNullEnabledQueriesTest extends
BaseQueriesTest {
assertEquals(thirdRow[0], false);
}
{
- String query =
- "SELECT COUNT(*) AS count, booleanColumn FROM testTable GROUP BY
booleanColumn ORDER BY booleanColumn";
+ String query = String.format("SELECT COUNT(*) AS count, %s FROM
testTable GROUP BY %s ORDER BY %s",
+ BOOLEAN_COLUMN, BOOLEAN_COLUMN, BOOLEAN_COLUMN);
BrokerResponseNative brokerResponse = getBrokerResponse(query,
queryOptions);
ResultTable resultTable = brokerResponse.getResultTable();
DataSchema dataSchema = resultTable.getDataSchema();
- assertEquals(dataSchema, new DataSchema(new String[]{"count",
"booleanColumn"},
+ assertEquals(dataSchema, new DataSchema(new String[]{"count",
BOOLEAN_COLUMN},
new ColumnDataType[]{ColumnDataType.LONG, ColumnDataType.BOOLEAN}));
List<Object[]> rows = resultTable.getRows();
assertEquals(rows.size(), 3);
Object[] firstRow = rows.get(0);
assertEquals(firstRow.length, 2);
- assertEquals(firstRow[0], (long) 1716);
- assertEquals(firstRow[1], false);
+ assertEquals(firstRow[0], (long) _falseValuesCount * 4);
+ assertFalse((boolean) firstRow[1]);
Object[] secondRow = rows.get(1);
assertEquals(secondRow.length, 2);
- assertEquals(secondRow[0], (long) 1716);
- assertEquals(secondRow[1], true);
+ assertEquals(secondRow[0], (long) _trueValuesCount * 4);
+ assertTrue((boolean) secondRow[1]);
Object[] thirdRow = rows.get(2);
assertEquals(thirdRow.length, 2);
- assertEquals(thirdRow[0], (long) 568);
+ assertEquals(thirdRow[0], (long) _nullValuesCount * 4);
assertNull(thirdRow[1]);
}
DataTableFactory.setDataTableVersion(DataTableFactory.VERSION_3);
diff --git
a/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkScanDocIdIterators.java
b/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkScanDocIdIterators.java
index 718d99e9ba..3e061c0d2d 100644
---
a/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkScanDocIdIterators.java
+++
b/pinot-perf/src/main/java/org/apache/pinot/perf/BenchmarkScanDocIdIterators.java
@@ -123,7 +123,7 @@ public class BenchmarkScanDocIdIterators {
@Benchmark
public MutableRoaringBitmap benchmarkSVLong() {
- return new SVScanDocIdIterator(_predicateEvaluator, _readerV2,
_numDocs).applyAnd(_bitmap);
+ return new SVScanDocIdIterator(_predicateEvaluator, _readerV2, _numDocs,
null).applyAnd(_bitmap);
}
public static class DummyPredicateEvaluator implements PredicateEvaluator {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]