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:
kaijianding 2017-02-24 00:07:59 +08:00 committed by Gian Merlino
parent ebd100cbb0
commit 7ce05d58bc
2 changed files with 77 additions and 2 deletions

View File

@ -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;
} }

View File

@ -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);