Fix filtering on boolean values in transformation (#9812) (#9828)
* Fix filter on boolean value in Transform
* assert
* more descriptive test
* remove assert
* add assert for cached string; disable tests
* typo
diff --git a/core/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java b/core/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java
index ea0ec67..0bbc1fc 100644
--- a/core/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java
+++ b/core/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java
@@ -60,7 +60,7 @@
)
{
this.period = Preconditions.checkNotNull(period, "period can't be null!");
- Preconditions.checkArgument(!Period.ZERO.equals(period), "zero period is not acceptable in QueryGranularity!");
+ Preconditions.checkArgument(!Period.ZERO.equals(period), "zero period is not acceptable in PeriodGranularity!");
this.chronology = tz == null ? ISOChronology.getInstanceUTC() : ISOChronology.getInstance(tz);
if (origin == null) {
// default to origin in given time zone when aligning multi-period granularities
diff --git a/core/src/main/java/org/apache/druid/math/expr/Expr.java b/core/src/main/java/org/apache/druid/math/expr/Expr.java
index 7ec75f9..2911c7f 100644
--- a/core/src/main/java/org/apache/druid/math/expr/Expr.java
+++ b/core/src/main/java/org/apache/druid/math/expr/Expr.java
@@ -1287,7 +1287,6 @@
return ExprEval.of(null);
}
-
if (leftVal.type() == ExprType.STRING && rightVal.type() == ExprType.STRING) {
return evalString(leftVal.asString(), rightVal.asString());
} else if (leftVal.type() == ExprType.LONG && rightVal.type() == ExprType.LONG) {
diff --git a/core/src/main/java/org/apache/druid/math/expr/ExprEval.java b/core/src/main/java/org/apache/druid/math/expr/ExprEval.java
index a993790..61cdc26 100644
--- a/core/src/main/java/org/apache/druid/math/expr/ExprEval.java
+++ b/core/src/main/java/org/apache/druid/math/expr/ExprEval.java
@@ -117,7 +117,7 @@
}
// Cached String values
- private boolean stringValueValid = false;
+ private boolean stringValueCached = false;
@Nullable
private String stringValue;
@@ -137,17 +137,35 @@
return value;
}
+ void cacheStringValue(@Nullable String value)
+ {
+ stringValue = value;
+ stringValueCached = true;
+ }
+
+ @Nullable
+ String getCachedStringValue()
+ {
+ assert stringValueCached;
+ return stringValue;
+ }
+
+ boolean isStringValueCached()
+ {
+ return stringValueCached;
+ }
+
@Nullable
public String asString()
{
- if (!stringValueValid) {
+ if (!stringValueCached) {
if (value == null) {
stringValue = null;
} else {
stringValue = String.valueOf(value);
}
- stringValueValid = true;
+ stringValueCached = true;
}
return stringValue;
@@ -568,6 +586,21 @@
}
@Override
+ @Nullable
+ public String asString()
+ {
+ if (!isStringValueCached()) {
+ if (value == null) {
+ cacheStringValue(null);
+ } else {
+ cacheStringValue(Arrays.toString(value));
+ }
+ }
+
+ return getCachedStringValue();
+ }
+
+ @Override
public boolean isNumericNull()
{
return false;
diff --git a/processing/src/main/java/org/apache/druid/segment/filter/PredicateValueMatcherFactory.java b/processing/src/main/java/org/apache/druid/segment/filter/PredicateValueMatcherFactory.java
index 9e4f1b0..b7f3f5d 100644
--- a/processing/src/main/java/org/apache/druid/segment/filter/PredicateValueMatcherFactory.java
+++ b/processing/src/main/java/org/apache/druid/segment/filter/PredicateValueMatcherFactory.java
@@ -116,11 +116,13 @@
} else if (rowValue instanceof Number) {
// Double or some other non-int, non-long, non-float number.
return getDoublePredicate().applyDouble((double) rowValue);
- } else if (rowValue instanceof String || rowValue instanceof List) {
- // String or list-of-something. Cast to list of strings and evaluate them as strings.
+ } else {
+ // Other types. Cast to list of strings and evaluate them as strings.
+ // Boolean values are handled here as well since it is not a known type in Druid.
final List<String> rowValueStrings = Rows.objectToStrings(rowValue);
if (rowValueStrings.isEmpty()) {
+ // Empty list is equivalent to null.
return getStringPredicate().apply(null);
}
@@ -131,9 +133,6 @@
}
return false;
- } else {
- // Unfilterable type. Treat as null.
- return getStringPredicate().apply(null);
}
}
diff --git a/processing/src/main/java/org/apache/druid/segment/virtual/ExpressionSelectors.java b/processing/src/main/java/org/apache/druid/segment/virtual/ExpressionSelectors.java
index 345274a..5ae6987 100644
--- a/processing/src/main/java/org/apache/druid/segment/virtual/ExpressionSelectors.java
+++ b/processing/src/main/java/org/apache/druid/segment/virtual/ExpressionSelectors.java
@@ -265,7 +265,6 @@
@Override
protected String getValue()
{
-
return NullHandling.emptyToNullIfNeeded(baseSelector.getObject().asString());
}
diff --git a/processing/src/test/java/org/apache/druid/segment/filter/PredicateValueMatcherFactoryTest.java b/processing/src/test/java/org/apache/druid/segment/filter/PredicateValueMatcherFactoryTest.java
new file mode 100644
index 0000000..df5a35e
--- /dev/null
+++ b/processing/src/test/java/org/apache/druid/segment/filter/PredicateValueMatcherFactoryTest.java
@@ -0,0 +1,429 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.segment.filter;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.druid.common.config.NullHandling;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.query.filter.SelectorPredicateFactory;
+import org.apache.druid.query.filter.ValueMatcher;
+import org.apache.druid.segment.DimensionSelector;
+import org.apache.druid.segment.SimpleAscendingOffset;
+import org.apache.druid.segment.column.ValueType;
+import org.apache.druid.segment.data.GenericIndexed;
+import org.apache.druid.segment.data.VSizeColumnarInts;
+import org.apache.druid.segment.data.VSizeColumnarMultiInts;
+import org.apache.druid.segment.selector.TestColumnValueSelector;
+import org.apache.druid.segment.serde.DictionaryEncodedColumnSupplier;
+import org.apache.druid.testing.InitializedNullHandlingTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.annotation.Nullable;
+import java.util.Arrays;
+
+public class PredicateValueMatcherFactoryTest extends InitializedNullHandlingTest
+{
+ @Test
+ public void testDefaultType()
+ {
+ Assert.assertEquals(ValueType.COMPLEX, forSelector(null).defaultType());
+ }
+
+ @Test
+ public void testDimensionProcessorSingleValuedDimensionMatchingValue()
+ {
+ final ValueMatcher matcher = forSelector("0").makeDimensionProcessor(DimensionSelector.constant("0"), false);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testDimensionProcessorSingleValuedDimensionNotMatchingValue()
+ {
+ final ValueMatcher matcher = forSelector("1").makeDimensionProcessor(DimensionSelector.constant("0"), false);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testDimensionProcessorMultiValuedDimensionMatchingValue()
+ {
+ // Emulate multi-valued dimension
+ final DictionaryEncodedColumnSupplier columnSupplier = new DictionaryEncodedColumnSupplier(
+ GenericIndexed.fromIterable(ImmutableList.of("v1", "v2", "v3"), GenericIndexed.STRING_STRATEGY),
+ null,
+ () -> VSizeColumnarMultiInts.fromIterable(ImmutableList.of(VSizeColumnarInts.fromArray(new int[]{1}))),
+ 0
+ );
+ final ValueMatcher matcher = forSelector("v2")
+ .makeDimensionProcessor(columnSupplier.get().makeDimensionSelector(new SimpleAscendingOffset(1), null), true);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testDimensionProcessorMultiValuedDimensionNotMatchingValue()
+ {
+ // Emulate multi-valued dimension
+ final DictionaryEncodedColumnSupplier columnSupplier = new DictionaryEncodedColumnSupplier(
+ GenericIndexed.fromIterable(ImmutableList.of("v1", "v2", "v3"), GenericIndexed.STRING_STRATEGY),
+ null,
+ () -> VSizeColumnarMultiInts.fromIterable(ImmutableList.of(VSizeColumnarInts.fromArray(new int[]{1}))),
+ 0
+ );
+ final ValueMatcher matcher = forSelector("v3")
+ .makeDimensionProcessor(columnSupplier.get().makeDimensionSelector(new SimpleAscendingOffset(1), null), true);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testFloatProcessorMatchingValue()
+ {
+ final TestColumnValueSelector<Float> columnValueSelector = TestColumnValueSelector.of(
+ Float.class,
+ ImmutableList.of(2.f),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("2.f").makeFloatProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testFloatProcessorNotMatchingValue()
+ {
+ final TestColumnValueSelector<Float> columnValueSelector = TestColumnValueSelector.of(
+ Float.class,
+ ImmutableList.of(2.f),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("5.f").makeFloatProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testDoubleProcessorMatchingValue()
+ {
+ final TestColumnValueSelector<Double> columnValueSelector = TestColumnValueSelector.of(
+ Double.class,
+ ImmutableList.of(2.),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("2.").makeDoubleProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testDoubleProcessorNotMatchingValue()
+ {
+ final TestColumnValueSelector<Double> columnValueSelector = TestColumnValueSelector.of(
+ Double.class,
+ ImmutableList.of(2.),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("5.").makeDoubleProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testLongProcessorMatchingValue()
+ {
+ final TestColumnValueSelector<Long> columnValueSelector = TestColumnValueSelector.of(
+ Long.class,
+ ImmutableList.of(2L),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("2").makeLongProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testLongProcessorNotMatchingValue()
+ {
+ final TestColumnValueSelector<Long> columnValueSelector = TestColumnValueSelector.of(
+ Long.class,
+ ImmutableList.of(2L),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("5").makeLongProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorMatchingNull()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ Arrays.asList(null, "v"),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector(null).makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorEmptyString()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ Arrays.asList("", "v"),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector(null).makeComplexProcessor(columnValueSelector);
+ if (NullHandling.sqlCompatible()) {
+ Assert.assertFalse(matcher.matches());
+ } else {
+ Assert.assertTrue(matcher.matches());
+ }
+ }
+
+ @Test
+ public void testComplexProcessorMatchingInteger()
+ {
+ final TestColumnValueSelector<Integer> columnValueSelector = TestColumnValueSelector.of(
+ Integer.class,
+ ImmutableList.of(11),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("11").makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorNotMatchingInteger()
+ {
+ final TestColumnValueSelector<Integer> columnValueSelector = TestColumnValueSelector.of(
+ Integer.class,
+ ImmutableList.of(15),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("11").makeComplexProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorMatchingLong()
+ {
+ final TestColumnValueSelector<Long> columnValueSelector = TestColumnValueSelector.of(
+ Long.class,
+ ImmutableList.of(11L),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("11").makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorNotMatchingLong()
+ {
+ final TestColumnValueSelector<Long> columnValueSelector = TestColumnValueSelector.of(
+ Long.class,
+ ImmutableList.of(15L),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("11").makeComplexProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorMatchingFloat()
+ {
+ final TestColumnValueSelector<Float> columnValueSelector = TestColumnValueSelector.of(
+ Float.class,
+ ImmutableList.of(11.f),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("11.f").makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorNotMatchingFloat()
+ {
+ final TestColumnValueSelector<Float> columnValueSelector = TestColumnValueSelector.of(
+ Float.class,
+ ImmutableList.of(15.f),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("11.f").makeComplexProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorMatchingDouble()
+ {
+ final TestColumnValueSelector<Double> columnValueSelector = TestColumnValueSelector.of(
+ Double.class,
+ ImmutableList.of(11.d),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("11.d").makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorNotMatchingDouble()
+ {
+ final TestColumnValueSelector<Double> columnValueSelector = TestColumnValueSelector.of(
+ Double.class,
+ ImmutableList.of(15.d),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("11.d").makeComplexProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorMatchingString()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ ImmutableList.of("val"),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("val").makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorNotMatchingString()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ ImmutableList.of("bar"),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("val").makeComplexProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorMatchingStringList()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ ImmutableList.of(ImmutableList.of("val")),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("val").makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorNotMatchingStringList()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ ImmutableList.of(ImmutableList.of("bar")),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("val").makeComplexProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorMatchingEmptyList()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ ImmutableList.of(ImmutableList.of()),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector(null).makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorMatchingBoolean()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ ImmutableList.of(false),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("false").makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorNotMatchingBoolean()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ ImmutableList.of(true),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("false").makeComplexProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorMatchingByteArray()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ ImmutableList.of(StringUtils.toUtf8("var")),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final String base64Encoded = StringUtils.encodeBase64String(StringUtils.toUtf8("var"));
+ final ValueMatcher matcher = forSelector(base64Encoded).makeComplexProcessor(columnValueSelector);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testComplexProcessorNotMatchingByteArray()
+ {
+ final TestColumnValueSelector<String> columnValueSelector = TestColumnValueSelector.of(
+ String.class,
+ ImmutableList.of(StringUtils.toUtf8("var")),
+ DateTimes.nowUtc()
+ );
+ columnValueSelector.advance();
+ final ValueMatcher matcher = forSelector("val").makeComplexProcessor(columnValueSelector);
+ Assert.assertFalse(matcher.matches());
+ }
+
+ private static PredicateValueMatcherFactory forSelector(@Nullable String value)
+ {
+ return new PredicateValueMatcherFactory(new SelectorPredicateFactory(value));
+ }
+}
diff --git a/processing/src/test/java/org/apache/druid/segment/selector/TestColumnValueSelector.java b/processing/src/test/java/org/apache/druid/segment/selector/TestColumnValueSelector.java
new file mode 100644
index 0000000..8e3ad9c
--- /dev/null
+++ b/processing/src/test/java/org/apache/druid/segment/selector/TestColumnValueSelector.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.segment.selector;
+
+import org.apache.druid.query.dimension.DimensionSpec;
+import org.apache.druid.query.monomorphicprocessing.RuntimeShapeInspector;
+import org.apache.druid.segment.ColumnSelectorFactory;
+import org.apache.druid.segment.ColumnValueSelector;
+import org.apache.druid.segment.Cursor;
+import org.apache.druid.segment.DimensionSelector;
+import org.apache.druid.segment.column.ColumnCapabilities;
+import org.joda.time.DateTime;
+
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+public class TestColumnValueSelector<T> implements ColumnValueSelector<Object>, Cursor
+{
+ private final Class<T> clazz;
+ private final Supplier<Iterator<Object>> iteratorSupplier;
+ private final DateTime time;
+
+ private Iterator<Object> iterator;
+ private Object value;
+
+ public static <T> TestColumnValueSelector<T> of(Class<T> clazz, Collection<Object> collection, DateTime time)
+ {
+ return new TestColumnValueSelector<>(clazz, collection::iterator, time);
+ }
+
+ public static <T> TestColumnValueSelector<T> of(Class<T> clazz, Stream<Object> stream, DateTime time)
+ {
+ return new TestColumnValueSelector<>(clazz, stream::iterator, time);
+ }
+
+ protected TestColumnValueSelector(Class<T> clazz, Supplier<Iterator<Object>> iteratorSupplier, DateTime time)
+ {
+ this.clazz = clazz;
+ this.iteratorSupplier = iteratorSupplier;
+ this.time = time;
+ this.iterator = iteratorSupplier.get();
+ }
+
+ @Override
+ public ColumnSelectorFactory getColumnSelectorFactory()
+ {
+ return new ColumnSelectorFactory()
+ {
+ @Override
+ public DimensionSelector makeDimensionSelector(DimensionSpec dimensionSpec)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public ColumnValueSelector makeColumnValueSelector(String columnName)
+ {
+ return TestColumnValueSelector.this;
+ }
+
+ @Nullable
+ @Override
+ public ColumnCapabilities getColumnCapabilities(String column)
+ {
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public DateTime getTime()
+ {
+ return time;
+ }
+
+ @Override
+ public void advance()
+ {
+ value = iterator.next();
+ }
+
+ @Override
+ public void advanceUninterruptibly()
+ {
+ advance();
+ }
+
+ @Override
+ public boolean isDone()
+ {
+ return !iterator.hasNext();
+ }
+
+ @Override
+ public boolean isDoneOrInterrupted()
+ {
+ return isDone();
+ }
+
+ @Override
+ public void reset()
+ {
+ iterator = iteratorSupplier.get();
+ }
+
+ @Override
+ public double getDouble()
+ {
+ if (value instanceof Number) {
+ return ((Number) value).doubleValue();
+ } else {
+ return Double.parseDouble(value.toString());
+ }
+ }
+
+ @Override
+ public float getFloat()
+ {
+ if (value instanceof Number) {
+ return ((Number) value).floatValue();
+ } else {
+ return Float.parseFloat(value.toString());
+ }
+ }
+
+ @Override
+ public long getLong()
+ {
+ if (value instanceof Number) {
+ return ((Number) value).longValue();
+ } else {
+ return Long.parseLong(value.toString());
+ }
+ }
+
+ @Override
+ public void inspectRuntimeShape(RuntimeShapeInspector inspector)
+ {
+ }
+
+ @Override
+ public boolean isNull()
+ {
+ return value == null;
+ }
+
+ @Nullable
+ @Override
+ public Object getObject()
+ {
+ return value;
+ }
+
+ @Override
+ public Class<? extends T> classOfObject()
+ {
+ return clazz;
+ }
+}
diff --git a/processing/src/test/java/org/apache/druid/segment/transform/TransformerTest.java b/processing/src/test/java/org/apache/druid/segment/transform/TransformerTest.java
new file mode 100644
index 0000000..5cf1b33
--- /dev/null
+++ b/processing/src/test/java/org/apache/druid/segment/transform/TransformerTest.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.segment.transform;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.druid.data.input.InputRow;
+import org.apache.druid.data.input.InputRowListPlusRawValues;
+import org.apache.druid.data.input.MapBasedInputRow;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.query.expression.TestExprMacroTable;
+import org.apache.druid.query.filter.SelectorDimFilter;
+import org.apache.druid.testing.InitializedNullHandlingTest;
+import org.joda.time.DateTime;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class TransformerTest extends InitializedNullHandlingTest
+{
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void testTransformNullRowReturnNull()
+ {
+ final Transformer transformer = new Transformer(new TransformSpec(null, null));
+ Assert.assertNull(transformer.transform((InputRow) null));
+ Assert.assertNull(transformer.transform((InputRowListPlusRawValues) null));
+ }
+
+ @Test
+ public void testTransformTimeColumn()
+ {
+ final Transformer transformer = new Transformer(
+ new TransformSpec(
+ null,
+ ImmutableList.of(
+ new ExpressionTransform("__time", "timestamp_shift(__time, 'P1D', -2)", TestExprMacroTable.INSTANCE)
+ )
+ )
+ );
+ final DateTime now = DateTimes.nowUtc();
+ final InputRow row = new MapBasedInputRow(
+ now,
+ ImmutableList.of("dim"),
+ ImmutableMap.of("__time", now, "dim", false)
+ );
+ final InputRow actual = transformer.transform(row);
+ Assert.assertNotNull(actual);
+ Assert.assertEquals(now.minusDays(2), actual.getTimestamp());
+ }
+
+ @Test
+ public void testTransformWithStringTransformOnBooleanColumnTransformAfterCasting()
+ {
+ final Transformer transformer = new Transformer(
+ new TransformSpec(
+ null,
+ ImmutableList.of(new ExpressionTransform("dim", "strlen(dim)", TestExprMacroTable.INSTANCE))
+ )
+ );
+ final InputRow row = new MapBasedInputRow(
+ DateTimes.nowUtc(),
+ ImmutableList.of("dim"),
+ ImmutableMap.of("dim", false)
+ );
+ final InputRow actual = transformer.transform(row);
+ Assert.assertNotNull(actual);
+ Assert.assertEquals(ImmutableList.of("dim"), actual.getDimensions());
+ Assert.assertEquals(5L, actual.getRaw("dim"));
+ Assert.assertEquals(row.getTimestamp(), actual.getTimestamp());
+ }
+
+ @Test
+ public void testTransformWithStringTransformOnLongColumnTransformAfterCasting()
+ {
+ final Transformer transformer = new Transformer(
+ new TransformSpec(
+ null,
+ ImmutableList.of(new ExpressionTransform("dim", "strlen(dim)", TestExprMacroTable.INSTANCE))
+ )
+ );
+ final InputRow row = new MapBasedInputRow(
+ DateTimes.nowUtc(),
+ ImmutableList.of("dim"),
+ ImmutableMap.of("dim", 10L)
+ );
+ final InputRow actual = transformer.transform(row);
+ Assert.assertNotNull(actual);
+ Assert.assertEquals(ImmutableList.of("dim"), actual.getDimensions());
+ Assert.assertEquals(2L, actual.getRaw("dim"));
+ Assert.assertEquals(row.getTimestamp(), actual.getTimestamp());
+ }
+
+ @Test
+ public void testTransformWithStringTransformOnDoubleColumnTransformAfterCasting()
+ {
+ final Transformer transformer = new Transformer(
+ new TransformSpec(
+ null,
+ ImmutableList.of(new ExpressionTransform("dim", "strlen(dim)", TestExprMacroTable.INSTANCE))
+ )
+ );
+ final InputRow row = new MapBasedInputRow(
+ DateTimes.nowUtc(),
+ ImmutableList.of("dim"),
+ ImmutableMap.of("dim", 200.5d)
+ );
+ final InputRow actual = transformer.transform(row);
+ Assert.assertNotNull(actual);
+ Assert.assertEquals(ImmutableList.of("dim"), actual.getDimensions());
+ Assert.assertEquals(5L, actual.getRaw("dim"));
+ Assert.assertEquals(row.getTimestamp(), actual.getTimestamp());
+ }
+
+ @Ignore("Disabled until https://github.com/apache/druid/issues/9824 is fixed")
+ @Test
+ public void testTransformWithStringTransformOnListColumnThrowingException()
+ {
+ final Transformer transformer = new Transformer(
+ new TransformSpec(
+ null,
+ ImmutableList.of(new ExpressionTransform("dim", "strlen(dim)", TestExprMacroTable.INSTANCE))
+ )
+ );
+ final InputRow row = new MapBasedInputRow(
+ DateTimes.nowUtc(),
+ ImmutableList.of("dim"),
+ ImmutableMap.of("dim", ImmutableList.of(10, 20, 100))
+ );
+ final InputRow actual = transformer.transform(row);
+ Assert.assertNotNull(actual);
+ Assert.assertEquals(ImmutableList.of("dim"), actual.getDimensions());
+ // Unlike for querying, Druid doesn't explode multi-valued columns automatically for ingestion.
+ expectedException.expect(AssertionError.class);
+ actual.getRaw("dim");
+ }
+
+ @Test
+ public void testTransformWithSelectorFilterWithStringBooleanValueOnBooleanColumnFilterAfterCasting()
+ {
+ final Transformer transformer = new Transformer(
+ new TransformSpec(new SelectorDimFilter("dim", "false", null), null)
+ );
+ final InputRow row1 = new MapBasedInputRow(
+ DateTimes.nowUtc(),
+ ImmutableList.of("dim"),
+ ImmutableMap.of("dim", false)
+ );
+ Assert.assertEquals(row1, transformer.transform(row1));
+ final InputRow row2 = new MapBasedInputRow(
+ DateTimes.nowUtc(),
+ ImmutableList.of("dim"),
+ ImmutableMap.of("dim", true)
+ );
+ Assert.assertNull(transformer.transform(row2));
+ }
+
+ @Test
+ public void testTransformWithSelectorFilterWithStringBooleanValueOnStringColumn()
+ {
+ final Transformer transformer = new Transformer(
+ new TransformSpec(new SelectorDimFilter("dim", "false", null), null)
+ );
+ final InputRow row = new MapBasedInputRow(
+ DateTimes.nowUtc(),
+ ImmutableList.of("dim"),
+ ImmutableMap.of("dim", "false")
+ );
+ Assert.assertEquals(row, transformer.transform(row));
+ final InputRow row2 = new MapBasedInputRow(
+ DateTimes.nowUtc(),
+ ImmutableList.of("dim"),
+ ImmutableMap.of("dim", "true")
+ );
+ Assert.assertNull(transformer.transform(row2));
+ }
+
+ @Test
+ public void testTransformWithTransformAndFilterTransformFirst()
+ {
+ final Transformer transformer = new Transformer(
+ new TransformSpec(
+ new SelectorDimFilter("dim", "0", null),
+ // A boolean expression returns a long.
+ ImmutableList.of(new ExpressionTransform("dim", "strlen(dim) == 10", TestExprMacroTable.INSTANCE))
+ )
+ );
+ final InputRow row = new MapBasedInputRow(
+ DateTimes.nowUtc(),
+ ImmutableList.of("dim"),
+ ImmutableMap.of("dim", "short")
+ );
+ final InputRow actual = transformer.transform(row);
+ Assert.assertNotNull(actual);
+ Assert.assertEquals(ImmutableList.of("dim"), actual.getDimensions());
+ Assert.assertEquals(0L, actual.getRaw("dim"));
+ Assert.assertEquals(row.getTimestamp(), actual.getTimestamp());
+ }
+}