NPE thrown when empty/null is passes to TimeDimExtractionFn (#4601)

* NPE thrown when empty/null is passes to TimeDimExtractionFn

* Add @Nullable where ever applicable

* Add @Nullable to SearchQuerySpec.apply()

* Remove unused
This commit is contained in:
Niketh Sabbineni 2017-07-26 19:02:08 -07:00 committed by Himanshu
parent 5929066dfb
commit da43f68e95
25 changed files with 123 additions and 38 deletions

View File

@ -56,8 +56,13 @@ public class BucketExtractionFn implements ExtractionFn
} }
@Override @Override
public String apply(Object value) @Nullable
public String apply(@Nullable Object value)
{ {
if (value == null) {
return null;
}
if (value instanceof Number) { if (value instanceof Number) {
return bucket(((Number) value).doubleValue()); return bucket(((Number) value).doubleValue());
} else if (value instanceof String) { } else if (value instanceof String) {
@ -67,8 +72,13 @@ public class BucketExtractionFn implements ExtractionFn
} }
@Override @Override
public String apply(String value) @Nullable
public String apply(@Nullable String value)
{ {
if (value == null) {
return null;
}
try { try {
return bucket(Double.parseDouble(value)); return bucket(Double.parseDouble(value));
} }

View File

@ -25,6 +25,7 @@ import com.google.common.base.Joiner;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import javax.annotation.Nullable;
import java.util.Arrays; import java.util.Arrays;
public class CascadeExtractionFn implements ExtractionFn public class CascadeExtractionFn implements ExtractionFn
@ -40,14 +41,16 @@ public class CascadeExtractionFn implements ExtractionFn
return new byte[0]; return new byte[0];
} }
@Nullable
@Override @Override
public String apply(Object value) public String apply(@Nullable Object value)
{ {
return null; return null;
} }
@Nullable
@Override @Override
public String apply(String value) public String apply(@Nullable String value)
{ {
return null; return null;
} }
@ -113,13 +116,15 @@ public class CascadeExtractionFn implements ExtractionFn
} }
@Override @Override
public String apply(Object value) @Nullable
public String apply(@Nullable Object value)
{ {
return chainedExtractionFn.apply(value); return chainedExtractionFn.apply(value);
} }
@Override @Override
public String apply(String value) @Nullable
public String apply(@Nullable String value)
{ {
return chainedExtractionFn.apply(value); return chainedExtractionFn.apply(value);
} }
@ -195,12 +200,14 @@ public class CascadeExtractionFn implements ExtractionFn
return (child != null) ? Bytes.concat(fnCacheKey, child.getCacheKey()) : fnCacheKey; return (child != null) ? Bytes.concat(fnCacheKey, child.getCacheKey()) : fnCacheKey;
} }
public String apply(Object value) @Nullable
public String apply(@Nullable Object value)
{ {
return fn.apply((child != null) ? child.apply(value) : value); return fn.apply((child != null) ? child.apply(value) : value);
} }
public String apply(String value) @Nullable
public String apply(@Nullable String value)
{ {
return fn.apply((child != null) ? child.apply(value) : value); return fn.apply((child != null) ? child.apply(value) : value);
} }

View File

@ -19,12 +19,14 @@
package io.druid.query.extraction; package io.druid.query.extraction;
import javax.annotation.Nullable;
import java.util.Objects; import java.util.Objects;
public abstract class DimExtractionFn implements ExtractionFn public abstract class DimExtractionFn implements ExtractionFn
{ {
@Override @Override
public String apply(Object value) @Nullable
public String apply(@Nullable Object value)
{ {
return apply(Objects.toString(value, null)); return apply(Objects.toString(value, null));
} }

View File

@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.druid.query.lookup.LookupExtractionFn; import io.druid.query.lookup.LookupExtractionFn;
import io.druid.query.lookup.RegisteredLookupExtractionFn; import io.druid.query.lookup.RegisteredLookupExtractionFn;
import javax.annotation.Nullable;
/** /**
*/ */
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@ -75,7 +77,8 @@ public interface ExtractionFn
* *
* @return a value that should be used instead of the original * @return a value that should be used instead of the original
*/ */
public String apply(Object value); @Nullable
public String apply(@Nullable Object value);
/** /**
* The "extraction" function. This should map a String value into some other String value. * The "extraction" function. This should map a String value into some other String value.
@ -87,7 +90,8 @@ public interface ExtractionFn
* *
* @return a value that should be used instead of the original * @return a value that should be used instead of the original
*/ */
public String apply(String value); @Nullable
public String apply(@Nullable String value);
/** /**
* The "extraction" function. This should map a long value into some String value. * The "extraction" function. This should map a long value into some String value.

View File

@ -105,7 +105,8 @@ public abstract class FunctionalExtraction extends DimExtractionFn
} }
@Override @Override
public String apply(String value) @Nullable
public String apply(@Nullable String value)
{ {
return extractionFunction.apply(value); return extractionFunction.apply(value);
} }

View File

@ -21,6 +21,8 @@ package io.druid.query.extraction;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import javax.annotation.Nullable;
public class IdentityExtractionFn implements ExtractionFn public class IdentityExtractionFn implements ExtractionFn
{ {
private static final IdentityExtractionFn instance = new IdentityExtractionFn(); private static final IdentityExtractionFn instance = new IdentityExtractionFn();
@ -37,13 +39,15 @@ public class IdentityExtractionFn implements ExtractionFn
} }
@Override @Override
public String apply(Object value) @Nullable
public String apply(@Nullable Object value)
{ {
return value == null ? null : Strings.emptyToNull(value.toString()); return value == null ? null : Strings.emptyToNull(value.toString());
} }
@Override @Override
public String apply(String value) @Nullable
public String apply(@Nullable String value)
{ {
return Strings.emptyToNull(value); return Strings.emptyToNull(value);
} }

View File

@ -32,6 +32,7 @@ import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory; import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.ScriptableObject;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
public class JavaScriptExtractionFn implements ExtractionFn public class JavaScriptExtractionFn implements ExtractionFn
@ -111,7 +112,8 @@ public class JavaScriptExtractionFn implements ExtractionFn
} }
@Override @Override
public String apply(Object value) @Nullable
public String apply(@Nullable Object value)
{ {
if (fn == null) { if (fn == null) {
throw new ISE("JavaScript is disabled"); throw new ISE("JavaScript is disabled");
@ -121,7 +123,8 @@ public class JavaScriptExtractionFn implements ExtractionFn
} }
@Override @Override
public String apply(String value) @Nullable
public String apply(@Nullable String value)
{ {
return this.apply((Object) Strings.emptyToNull(value)); return this.apply((Object) Strings.emptyToNull(value));
} }

View File

@ -50,7 +50,7 @@ public class LowerExtractionFn extends DimExtractionFn
@Nullable @Nullable
@Override @Override
public String apply(String key) public String apply(@Nullable String key)
{ {
if (Strings.isNullOrEmpty(key)) { if (Strings.isNullOrEmpty(key)) {
return null; return null;

View File

@ -25,6 +25,7 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import io.druid.java.util.common.StringUtils; import io.druid.java.util.common.StringUtils;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -57,8 +58,9 @@ public class MatchingDimExtractionFn extends DimExtractionFn
.array(); .array();
} }
@Nullable
@Override @Override
public String apply(String dimValue) public String apply(@Nullable String dimValue)
{ {
if (Strings.isNullOrEmpty(dimValue)) { if (Strings.isNullOrEmpty(dimValue)) {
// We'd return null whether or not the pattern matched // We'd return null whether or not the pattern matched

View File

@ -26,6 +26,7 @@ import com.google.common.base.Strings;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import io.druid.java.util.common.StringUtils; import io.druid.java.util.common.StringUtils;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -100,8 +101,9 @@ public class RegexDimExtractionFn extends DimExtractionFn
.array(); .array();
} }
@Nullable
@Override @Override
public String apply(String dimValue) public String apply(@Nullable String dimValue)
{ {
final String retVal; final String retVal;
final Matcher matcher = pattern.matcher(Strings.nullToEmpty(dimValue)); final Matcher matcher = pattern.matcher(Strings.nullToEmpty(dimValue));

View File

@ -25,6 +25,7 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import io.druid.query.search.search.SearchQuerySpec; import io.druid.query.search.search.SearchQuerySpec;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
@ -59,8 +60,9 @@ public class SearchQuerySpecDimExtractionFn extends DimExtractionFn
.array(); .array();
} }
@Nullable
@Override @Override
public String apply(String dimValue) public String apply(@Nullable String dimValue)
{ {
return searchQuerySpec.accept(dimValue) ? Strings.emptyToNull(dimValue) : null; return searchQuerySpec.accept(dimValue) ? Strings.emptyToNull(dimValue) : null;
} }

View File

@ -26,6 +26,7 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import io.druid.java.util.common.StringUtils; import io.druid.java.util.common.StringUtils;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
@ -94,8 +95,9 @@ public class StringFormatExtractionFn extends DimExtractionFn
.array(); .array();
} }
@Nullable
@Override @Override
public String apply(String value) public String apply(@Nullable String value)
{ {
if (value == null) { if (value == null) {
if (nullHandling == NullHandling.RETURNNULL) { if (nullHandling == NullHandling.RETURNNULL) {

View File

@ -21,6 +21,8 @@ package io.druid.query.extraction;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import javax.annotation.Nullable;
public class StrlenExtractionFn extends DimExtractionFn public class StrlenExtractionFn extends DimExtractionFn
{ {
private static final StrlenExtractionFn INSTANCE = new StrlenExtractionFn(); private static final StrlenExtractionFn INSTANCE = new StrlenExtractionFn();
@ -36,7 +38,7 @@ public class StrlenExtractionFn extends DimExtractionFn
} }
@Override @Override
public String apply(String value) public String apply(@Nullable String value)
{ {
return String.valueOf(value == null ? 0 : value.length()); return String.valueOf(value == null ? 0 : value.length());
} }

View File

@ -61,8 +61,9 @@ public class SubstringDimExtractionFn extends DimExtractionFn
.array(); .array();
} }
@Nullable
@Override @Override
public String apply(String dimValue) public String apply(@Nullable String dimValue)
{ {
if (Strings.isNullOrEmpty(dimValue)) { if (Strings.isNullOrEmpty(dimValue)) {
return null; return null;

View File

@ -22,9 +22,11 @@ package io.druid.query.extraction;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.ibm.icu.text.SimpleDateFormat; import com.ibm.icu.text.SimpleDateFormat;
import io.druid.java.util.common.StringUtils; import io.druid.java.util.common.StringUtils;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.text.ParseException; import java.text.ParseException;
import java.util.Date; import java.util.Date;
@ -71,9 +73,14 @@ public class TimeDimExtractionFn extends DimExtractionFn
.array(); .array();
} }
@Nullable
@Override @Override
public String apply(String dimValue) public String apply(@Nullable String dimValue)
{ {
if (Strings.isNullOrEmpty(dimValue)) {
return null;
}
Date date; Date date;
try { try {
date = timeFormatter.get().parse(dimValue); date = timeFormatter.get().parse(dimValue);

View File

@ -31,6 +31,7 @@ import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat; import org.joda.time.format.ISODateTimeFormat;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Locale; import java.util.Locale;
@ -128,8 +129,13 @@ public class TimeFormatExtractionFn implements ExtractionFn
} }
@Override @Override
public String apply(Object value) @Nullable
public String apply(@Nullable Object value)
{ {
if (value == null) {
return null;
}
if (asMillis && value instanceof String) { if (asMillis && value instanceof String) {
final Long theLong = GuavaUtils.tryParseLong((String) value); final Long theLong = GuavaUtils.tryParseLong((String) value);
return theLong == null ? apply(new DateTime(value).getMillis()) : apply(theLong.longValue()); return theLong == null ? apply(new DateTime(value).getMillis()) : apply(theLong.longValue());
@ -139,7 +145,8 @@ public class TimeFormatExtractionFn implements ExtractionFn
} }
@Override @Override
public String apply(String value) @Nullable
public String apply(@Nullable String value)
{ {
return apply((Object) value); return apply((Object) value);
} }

View File

@ -49,7 +49,7 @@ public class UpperExtractionFn extends DimExtractionFn
*/ */
@Nullable @Nullable
@Override @Override
public String apply(String key) public String apply(@Nullable String key)
{ {
if (Strings.isNullOrEmpty(key)) { if (Strings.isNullOrEmpty(key)) {
return null; return null;

View File

@ -105,13 +105,15 @@ public class RegisteredLookupExtractionFn implements ExtractionFn
} }
@Override @Override
public String apply(Object value) @Nullable
public String apply(@Nullable Object value)
{ {
return ensureDelegate().apply(value); return ensureDelegate().apply(value);
} }
@Override @Override
public String apply(String value) @Nullable
public String apply(@Nullable String value)
{ {
return ensureDelegate().apply(value); return ensureDelegate().apply(value);
} }

View File

@ -19,6 +19,8 @@
package io.druid.query.search.search; package io.druid.query.search.search;
import javax.annotation.Nullable;
/** /**
*/ */
public class AllSearchQuerySpec implements SearchQuerySpec public class AllSearchQuerySpec implements SearchQuerySpec
@ -26,7 +28,7 @@ public class AllSearchQuerySpec implements SearchQuerySpec
private static final byte CACHE_TYPE_ID = 0x7f; private static final byte CACHE_TYPE_ID = 0x7f;
@Override @Override
public boolean accept(String dimVal) public boolean accept(@Nullable String dimVal)
{ {
return true; return true;
} }

View File

@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import io.druid.java.util.common.StringUtils; import io.druid.java.util.common.StringUtils;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
@ -59,7 +60,7 @@ public class ContainsSearchQuerySpec implements SearchQuerySpec
} }
@Override @Override
public boolean accept(String dimVal) public boolean accept(@Nullable String dimVal)
{ {
if (dimVal == null || value == null) { if (dimVal == null || value == null) {
return false; return false;

View File

@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.druid.java.util.common.StringUtils; import io.druid.java.util.common.StringUtils;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -77,7 +78,7 @@ public class FragmentSearchQuerySpec implements SearchQuerySpec
} }
@Override @Override
public boolean accept(String dimVal) public boolean accept(@Nullable String dimVal)
{ {
if (dimVal == null || values == null) { if (dimVal == null || values == null) {
return false; return false;

View File

@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.druid.java.util.common.StringUtils; import io.druid.java.util.common.StringUtils;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -74,7 +75,7 @@ public class RegexSearchQuerySpec implements SearchQuerySpec
} }
@Override @Override
public boolean accept(String dimVal) public boolean accept(@Nullable String dimVal)
{ {
if (dimVal == null) { if (dimVal == null) {
return false; return false;

View File

@ -22,6 +22,8 @@ package io.druid.query.search.search;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import javax.annotation.Nullable;
/** /**
*/ */
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@ -34,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
}) })
public interface SearchQuerySpec public interface SearchQuerySpec
{ {
public boolean accept(String dimVal); public boolean accept(@Nullable String dimVal);
public byte[] getCacheKey(); public byte[] getCacheKey();
} }

View File

@ -40,6 +40,16 @@ public class TimeDimExtractionFnTest
"12/21/2012" "12/21/2012"
}; };
@Test
public void testEmptyAndNullExtraction()
{
Set<String> testPeriod = Sets.newHashSet();
ExtractionFn extractionFn = new TimeDimExtractionFn("MM/dd/yyyy", "MM/yyyy");
Assert.assertNull(extractionFn.apply(null));
Assert.assertNull(extractionFn.apply(""));
}
@Test @Test
public void testMonthExtraction() public void testMonthExtraction()
{ {

View File

@ -30,6 +30,7 @@ import io.druid.data.input.impl.TimeAndDimsParseSpec;
import io.druid.data.input.impl.TimestampSpec; import io.druid.data.input.impl.TimestampSpec;
import io.druid.java.util.common.Pair; import io.druid.java.util.common.Pair;
import io.druid.query.extraction.MapLookupExtractor; import io.druid.query.extraction.MapLookupExtractor;
import io.druid.query.extraction.TimeDimExtractionFn;
import io.druid.query.filter.ExtractionDimFilter; import io.druid.query.filter.ExtractionDimFilter;
import io.druid.query.filter.InDimFilter; import io.druid.query.filter.InDimFilter;
import io.druid.query.filter.SelectorDimFilter; import io.druid.query.filter.SelectorDimFilter;
@ -58,7 +59,7 @@ public class SelectorFilterTest extends BaseFilterTest
new TimeAndDimsParseSpec( new TimeAndDimsParseSpec(
new TimestampSpec(TIMESTAMP_COLUMN, "iso", new DateTime("2000")), new TimestampSpec(TIMESTAMP_COLUMN, "iso", new DateTime("2000")),
new DimensionsSpec( new DimensionsSpec(
DimensionsSpec.getDefaultSchemas(ImmutableList.of("dim0", "dim1", "dim2", "dim3")), DimensionsSpec.getDefaultSchemas(ImmutableList.of("dim0", "dim1", "dim2", "dim3", "dim6")),
null, null,
null null
) )
@ -66,9 +67,9 @@ public class SelectorFilterTest extends BaseFilterTest
); );
private static final List<InputRow> ROWS = ImmutableList.of( private static final List<InputRow> ROWS = ImmutableList.of(
PARSER.parse(ImmutableMap.<String, Object>of("dim0", "0", "dim1", "", "dim2", ImmutableList.of("a", "b"))), PARSER.parse(ImmutableMap.<String, Object>of("dim0", "0", "dim1", "", "dim2", ImmutableList.of("a", "b"), "dim6", "2017-07-25")),
PARSER.parse(ImmutableMap.<String, Object>of("dim0", "1", "dim1", "10", "dim2", ImmutableList.of())), PARSER.parse(ImmutableMap.<String, Object>of("dim0", "1", "dim1", "10", "dim2", ImmutableList.of(), "dim6", "2017-07-25")),
PARSER.parse(ImmutableMap.<String, Object>of("dim0", "2", "dim1", "2", "dim2", ImmutableList.of(""))), PARSER.parse(ImmutableMap.<String, Object>of("dim0", "2", "dim1", "2", "dim2", ImmutableList.of(""), "dim6", "2017-05-25")),
PARSER.parse(ImmutableMap.<String, Object>of("dim0", "3", "dim1", "1", "dim2", ImmutableList.of("a"))), PARSER.parse(ImmutableMap.<String, Object>of("dim0", "3", "dim1", "1", "dim2", ImmutableList.of("a"))),
PARSER.parse(ImmutableMap.<String, Object>of("dim0", "4", "dim1", "def", "dim2", ImmutableList.of("c"))), PARSER.parse(ImmutableMap.<String, Object>of("dim0", "4", "dim1", "def", "dim2", ImmutableList.of("c"))),
PARSER.parse(ImmutableMap.<String, Object>of("dim0", "5", "dim1", "abc")) PARSER.parse(ImmutableMap.<String, Object>of("dim0", "5", "dim1", "abc"))
@ -91,6 +92,15 @@ public class SelectorFilterTest extends BaseFilterTest
BaseFilterTest.tearDown(SelectorFilterTest.class.getName()); BaseFilterTest.tearDown(SelectorFilterTest.class.getName());
} }
@Test
public void testWithTimeExtractionFnNull()
{
assertFilterMatches(new SelectorDimFilter("dim0", null, new TimeDimExtractionFn("yyyy-mm-dd", "yyyy-mm")), ImmutableList.<String>of());
assertFilterMatches(new SelectorDimFilter("dim6", null, new TimeDimExtractionFn("yyyy-mm-dd", "yyyy-mm")), ImmutableList.<String>of("3", "4", "5"));
assertFilterMatches(new SelectorDimFilter("dim6", "2017-07", new TimeDimExtractionFn("yyyy-mm-dd", "yyyy-mm")), ImmutableList.<String>of("0", "1"));
assertFilterMatches(new SelectorDimFilter("dim6", "2017-05", new TimeDimExtractionFn("yyyy-mm-dd", "yyyy-mm")), ImmutableList.<String>of("2"));
}
@Test @Test
public void testSingleValueStringColumnWithoutNulls() public void testSingleValueStringColumnWithoutNulls()
{ {