mirror of https://github.com/apache/druid.git
fix NPE in search query when dimension contains null value (#3968)
* fix NPE when dimension contains null value in search query * add ut * search with not existed dimension should always return empty result
This commit is contained in:
parent
ebd100cbb0
commit
7ce05d58bc
|
@ -20,6 +20,7 @@
|
||||||
package io.druid.query.search;
|
package io.druid.query.search;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
@ -41,6 +42,7 @@ import io.druid.segment.ColumnValueSelector;
|
||||||
import io.druid.segment.DimensionSelector;
|
import io.druid.segment.DimensionSelector;
|
||||||
import io.druid.segment.FloatColumnSelector;
|
import io.druid.segment.FloatColumnSelector;
|
||||||
import io.druid.segment.LongColumnSelector;
|
import io.druid.segment.LongColumnSelector;
|
||||||
|
import io.druid.segment.NullDimensionSelector;
|
||||||
import io.druid.segment.Segment;
|
import io.druid.segment.Segment;
|
||||||
import io.druid.segment.column.ColumnCapabilities;
|
import io.druid.segment.column.ColumnCapabilities;
|
||||||
import io.druid.segment.column.ValueType;
|
import io.druid.segment.column.ValueType;
|
||||||
|
@ -126,12 +128,12 @@ public class SearchQueryRunner implements QueryRunner<Result<SearchResultValue>>
|
||||||
final Object2IntRBTreeMap<SearchHit> set
|
final Object2IntRBTreeMap<SearchHit> set
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (selector != null) {
|
if (selector != null && !(selector instanceof NullDimensionSelector)) {
|
||||||
final IndexedInts vals = selector.getRow();
|
final IndexedInts vals = selector.getRow();
|
||||||
for (int i = 0; i < vals.size(); ++i) {
|
for (int i = 0; i < vals.size(); ++i) {
|
||||||
final String dimVal = selector.lookupName(vals.get(i));
|
final String dimVal = selector.lookupName(vals.get(i));
|
||||||
if (searchQuerySpec.accept(dimVal)) {
|
if (searchQuerySpec.accept(dimVal)) {
|
||||||
set.addTo(new SearchHit(outputName, dimVal), 1);
|
set.addTo(new SearchHit(outputName, Strings.nullToEmpty(dimVal)), 1);
|
||||||
if (set.size() >= limit) {
|
if (set.size() >= limit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ package io.druid.query.search;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import io.druid.data.input.MapBasedInputRow;
|
||||||
|
import io.druid.granularity.QueryGranularities;
|
||||||
import io.druid.java.util.common.guava.Sequence;
|
import io.druid.java.util.common.guava.Sequence;
|
||||||
import io.druid.java.util.common.guava.Sequences;
|
import io.druid.java.util.common.guava.Sequences;
|
||||||
import io.druid.java.util.common.logger.Logger;
|
import io.druid.java.util.common.logger.Logger;
|
||||||
|
@ -29,8 +31,10 @@ import io.druid.js.JavaScriptConfig;
|
||||||
import io.druid.query.Druids;
|
import io.druid.query.Druids;
|
||||||
import io.druid.query.Query;
|
import io.druid.query.Query;
|
||||||
import io.druid.query.QueryRunner;
|
import io.druid.query.QueryRunner;
|
||||||
|
import io.druid.query.QueryRunnerFactory;
|
||||||
import io.druid.query.QueryRunnerTestHelper;
|
import io.druid.query.QueryRunnerTestHelper;
|
||||||
import io.druid.query.Result;
|
import io.druid.query.Result;
|
||||||
|
import io.druid.query.aggregation.Aggregator;
|
||||||
import io.druid.query.dimension.DefaultDimensionSpec;
|
import io.druid.query.dimension.DefaultDimensionSpec;
|
||||||
import io.druid.query.dimension.ExtractionDimensionSpec;
|
import io.druid.query.dimension.ExtractionDimensionSpec;
|
||||||
import io.druid.query.extraction.ExtractionFn;
|
import io.druid.query.extraction.ExtractionFn;
|
||||||
|
@ -49,9 +53,14 @@ import io.druid.query.search.search.SearchQuery;
|
||||||
import io.druid.query.search.search.SearchQueryConfig;
|
import io.druid.query.search.search.SearchQueryConfig;
|
||||||
import io.druid.query.search.search.SearchSortSpec;
|
import io.druid.query.search.search.SearchSortSpec;
|
||||||
import io.druid.query.spec.MultipleIntervalSegmentSpec;
|
import io.druid.query.spec.MultipleIntervalSegmentSpec;
|
||||||
|
import io.druid.segment.QueryableIndexSegment;
|
||||||
import io.druid.segment.TestHelper;
|
import io.druid.segment.TestHelper;
|
||||||
|
import io.druid.segment.TestIndex;
|
||||||
import io.druid.segment.column.Column;
|
import io.druid.segment.column.Column;
|
||||||
import io.druid.segment.column.ValueType;
|
import io.druid.segment.column.ValueType;
|
||||||
|
import io.druid.segment.incremental.IncrementalIndex;
|
||||||
|
import io.druid.segment.incremental.IncrementalIndexSchema;
|
||||||
|
import io.druid.segment.incremental.OnheapIncrementalIndex;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -732,6 +741,70 @@ public class SearchQueryRunnerTest
|
||||||
checkSearchQuery(searchQuery, expectedHits);
|
checkSearchQuery(searchQuery, expectedHits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithNullValueInDimension() throws Exception
|
||||||
|
{
|
||||||
|
IncrementalIndex<Aggregator> index = new OnheapIncrementalIndex(
|
||||||
|
new IncrementalIndexSchema.Builder()
|
||||||
|
.withQueryGranularity(QueryGranularities.NONE)
|
||||||
|
.withMinTimestamp(new DateTime("2011-01-12T00:00:00.000Z").getMillis()).build(),
|
||||||
|
true,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
index.add(
|
||||||
|
new MapBasedInputRow(
|
||||||
|
1481871600000L,
|
||||||
|
Arrays.asList("name", "host"),
|
||||||
|
ImmutableMap.<String, Object>of("name", "name1", "host", "host")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
index.add(
|
||||||
|
new MapBasedInputRow(
|
||||||
|
1481871670000L,
|
||||||
|
Arrays.asList("name", "table"),
|
||||||
|
ImmutableMap.<String, Object>of("name", "name2", "table", "table")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SearchQuery searchQuery = Druids.newSearchQueryBuilder()
|
||||||
|
.dimensions(
|
||||||
|
new DefaultDimensionSpec("table", "table")
|
||||||
|
)
|
||||||
|
.dataSource(QueryRunnerTestHelper.dataSource)
|
||||||
|
.granularity(QueryRunnerTestHelper.allGran)
|
||||||
|
.intervals(QueryRunnerTestHelper.fullOnInterval)
|
||||||
|
// simulate when cardinality is big enough to fallback to cursorOnly strategy
|
||||||
|
.context(ImmutableMap.<String, Object>of("searchStrategy", "cursorOnly"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
QueryRunnerFactory factory = new SearchQueryRunnerFactory(
|
||||||
|
selector,
|
||||||
|
toolChest,
|
||||||
|
QueryRunnerTestHelper.NOOP_QUERYWATCHER
|
||||||
|
);
|
||||||
|
QueryRunner runner = factory.createRunner(new QueryableIndexSegment("asdf", TestIndex.persistRealtimeAndLoadMMapped(index)));
|
||||||
|
List<SearchHit> expectedHits = Lists.newLinkedList();
|
||||||
|
expectedHits.add(new SearchHit("table", "table", 1));
|
||||||
|
expectedHits.add(new SearchHit("table", "", 1));
|
||||||
|
checkSearchQuery(searchQuery, runner, expectedHits);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithNotExistedDimension() throws Exception
|
||||||
|
{
|
||||||
|
SearchQuery searchQuery = Druids.newSearchQueryBuilder()
|
||||||
|
.dimensions(
|
||||||
|
new DefaultDimensionSpec("asdf", "asdf")
|
||||||
|
)
|
||||||
|
.dataSource(QueryRunnerTestHelper.dataSource)
|
||||||
|
.granularity(QueryRunnerTestHelper.allGran)
|
||||||
|
.intervals(QueryRunnerTestHelper.fullOnInterval)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<SearchHit> noHit = Lists.newLinkedList();
|
||||||
|
checkSearchQuery(searchQuery, noHit);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkSearchQuery(Query searchQuery, List<SearchHit> expectedResults)
|
private void checkSearchQuery(Query searchQuery, List<SearchHit> expectedResults)
|
||||||
{
|
{
|
||||||
checkSearchQuery(searchQuery, runner, expectedResults);
|
checkSearchQuery(searchQuery, runner, expectedResults);
|
||||||
|
|
Loading…
Reference in New Issue