blob: b7f3f5dd1dca8ac7fc29f8a65fb936e23a1ad3ca [file] [log] [blame]
/*
* 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.base.Predicate;
import org.apache.druid.common.config.NullHandling;
import org.apache.druid.data.input.Rows;
import org.apache.druid.query.filter.DruidDoublePredicate;
import org.apache.druid.query.filter.DruidFloatPredicate;
import org.apache.druid.query.filter.DruidLongPredicate;
import org.apache.druid.query.filter.DruidPredicateFactory;
import org.apache.druid.query.filter.ValueMatcher;
import org.apache.druid.query.monomorphicprocessing.RuntimeShapeInspector;
import org.apache.druid.segment.BaseDoubleColumnValueSelector;
import org.apache.druid.segment.BaseFloatColumnValueSelector;
import org.apache.druid.segment.BaseLongColumnValueSelector;
import org.apache.druid.segment.BaseObjectColumnValueSelector;
import org.apache.druid.segment.ColumnProcessorFactory;
import org.apache.druid.segment.DimensionSelector;
import org.apache.druid.segment.NilColumnValueSelector;
import org.apache.druid.segment.column.ValueType;
import java.util.List;
/**
* Creates {@link ValueMatcher} that apply a predicate to each value.
*/
public class PredicateValueMatcherFactory implements ColumnProcessorFactory<ValueMatcher>
{
private final DruidPredicateFactory predicateFactory;
PredicateValueMatcherFactory(DruidPredicateFactory predicateFactory)
{
this.predicateFactory = predicateFactory;
}
@Override
public ValueType defaultType()
{
// Set default type to COMPLEX, so when the underlying type is unknown, we go into "makeComplexProcessor", which
// uses per-row type detection.
return ValueType.COMPLEX;
}
@Override
public ValueMatcher makeDimensionProcessor(DimensionSelector selector, boolean multiValue)
{
return ValueMatchers.makeStringValueMatcher(selector, predicateFactory, multiValue);
}
@Override
public ValueMatcher makeFloatProcessor(BaseFloatColumnValueSelector selector)
{
return ValueMatchers.makeFloatValueMatcher(selector, predicateFactory);
}
@Override
public ValueMatcher makeDoubleProcessor(BaseDoubleColumnValueSelector selector)
{
return ValueMatchers.makeDoubleValueMatcher(selector, predicateFactory);
}
@Override
public ValueMatcher makeLongProcessor(BaseLongColumnValueSelector selector)
{
return ValueMatchers.makeLongValueMatcher(selector, predicateFactory);
}
@Override
public ValueMatcher makeComplexProcessor(BaseObjectColumnValueSelector<?> selector)
{
if (selector instanceof NilColumnValueSelector || !mayBeFilterable(selector.classOfObject())) {
// Column does not exist, or is unfilterable. Treat it as all nulls.
return BooleanValueMatcher.of(predicateFactory.makeStringPredicate().apply(null));
} else {
// Column exists but the type of value is unknown (we might have got here because "defaultType" is COMPLEX).
// Return a ValueMatcher that inspects the object and does type-based comparison.
return new ValueMatcher()
{
private Predicate<String> stringPredicate;
private DruidLongPredicate longPredicate;
private DruidFloatPredicate floatPredicate;
private DruidDoublePredicate doublePredicate;
@Override
public boolean matches()
{
final Object rowValue = selector.getObject();
if (rowValue == null) {
return getStringPredicate().apply(null);
} else if (rowValue instanceof Integer) {
return getLongPredicate().applyLong((int) rowValue);
} else if (rowValue instanceof Long) {
return getLongPredicate().applyLong((long) rowValue);
} else if (rowValue instanceof Float) {
return getFloatPredicate().applyFloat((float) rowValue);
} else if (rowValue instanceof Number) {
// Double or some other non-int, non-long, non-float number.
return getDoublePredicate().applyDouble((double) rowValue);
} 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);
}
for (String rowValueString : rowValueStrings) {
if (getStringPredicate().apply(NullHandling.emptyToNullIfNeeded(rowValueString))) {
return true;
}
}
return false;
}
}
@Override
public void inspectRuntimeShape(RuntimeShapeInspector inspector)
{
inspector.visit("selector", selector);
inspector.visit("value", predicateFactory);
}
private Predicate<String> getStringPredicate()
{
if (stringPredicate == null) {
stringPredicate = predicateFactory.makeStringPredicate();
}
return stringPredicate;
}
private DruidLongPredicate getLongPredicate()
{
if (longPredicate == null) {
longPredicate = predicateFactory.makeLongPredicate();
}
return longPredicate;
}
private DruidFloatPredicate getFloatPredicate()
{
if (floatPredicate == null) {
floatPredicate = predicateFactory.makeFloatPredicate();
}
return floatPredicate;
}
private DruidDoublePredicate getDoublePredicate()
{
if (doublePredicate == null) {
doublePredicate = predicateFactory.makeDoublePredicate();
}
return doublePredicate;
}
};
}
}
/**
* Returns whether a {@link BaseObjectColumnValueSelector} with object class {@code clazz} might be filterable, i.e.,
* whether it might return numbers or strings.
*
* @param clazz class of object
*/
private static <T> boolean mayBeFilterable(final Class<T> clazz)
{
if (Number.class.isAssignableFrom(clazz) || String.class.isAssignableFrom(clazz)) {
// clazz is a Number or String.
return true;
} else if (clazz.isAssignableFrom(Number.class) || clazz.isAssignableFrom(String.class)) {
// clazz is a superclass of Number or String.
return true;
} else {
// Instances of clazz cannot possibly be Numbers or Strings.
return false;
}
}
}