in sql/src/main/java/org/apache/druid/sql/calcite/expression/Expressions.java [516:830]
private static DimFilter toSimpleLeafFilter(
final PlannerContext plannerContext,
final RowSignature rowSignature,
@Nullable final VirtualColumnRegistry virtualColumnRegistry,
final RexNode rexNode
)
{
final SqlKind kind = rexNode.getKind();
if (kind == SqlKind.IS_TRUE || kind == SqlKind.IS_NOT_FALSE || kind == SqlKind.IS_FALSE || kind == SqlKind.IS_NOT_TRUE) {
// use expression filter to get istrue/notfalse/isfalse/nottrue expressions for correct 3vl behavior
return toExpressionLeafFilter(plannerContext, rowSignature, rexNode);
} else if (kind == SqlKind.IS_NULL || kind == SqlKind.IS_NOT_NULL) {
final RexNode operand = Iterables.getOnlyElement(((RexCall) rexNode).getOperands());
final DruidExpression druidExpression = toDruidExpression(plannerContext, rowSignature, operand);
if (druidExpression == null) {
return null;
}
final DimFilter equalFilter;
final ColumnType outputType = druidExpression.getDruidType();
final boolean isOutputNumeric = Types.isNumeric(outputType);
// if a simple extraction, we can typically use the base column directly for filtering. however, some expressions
// such as cast also appear as a simple extraction because some native layer things can handle the cast
// themselves, so we check the output type of the expression and compare it to the type of the direct column. a
// string column might produce additional null values when converting to a number, so we should use the virtual
// column instead for filtering to ensure that results are correct
if (druidExpression.isSimpleExtraction() &&
!(isOutputNumeric && !rowSignature.isNumeric(druidExpression.getDirectColumn()))) {
if (plannerContext.isUseBoundsAndSelectors()) {
equalFilter = new SelectorDimFilter(
druidExpression.getSimpleExtraction().getColumn(),
null,
druidExpression.getSimpleExtraction().getExtractionFn()
);
} else {
if (druidExpression.getSimpleExtraction().getExtractionFn() != null) {
if (virtualColumnRegistry != null) {
String column = virtualColumnRegistry.getOrCreateVirtualColumnForExpression(
druidExpression,
druidExpression.getDruidType()
);
equalFilter = NullFilter.forColumn(column);
} else {
// virtual column registry unavailable, fallback to expression filter
return null;
}
} else {
equalFilter = NullFilter.forColumn(druidExpression.getDirectColumn());
}
}
} else if (virtualColumnRegistry != null) {
final String virtualColumn = virtualColumnRegistry.getOrCreateVirtualColumnForExpression(
druidExpression,
operand.getType()
);
if (plannerContext.isUseBoundsAndSelectors()) {
equalFilter = new SelectorDimFilter(virtualColumn, null, null);
} else {
equalFilter = NullFilter.forColumn(virtualColumn);
}
} else {
return null;
}
return kind == SqlKind.IS_NOT_NULL ? new NotDimFilter(equalFilter) : equalFilter;
} else if (kind == SqlKind.EQUALS
|| kind == SqlKind.NOT_EQUALS
|| kind == SqlKind.IS_NOT_DISTINCT_FROM
|| kind == SqlKind.IS_DISTINCT_FROM
|| kind == SqlKind.GREATER_THAN
|| kind == SqlKind.GREATER_THAN_OR_EQUAL
|| kind == SqlKind.LESS_THAN
|| kind == SqlKind.LESS_THAN_OR_EQUAL) {
final List<RexNode> operands = ((RexCall) rexNode).getOperands();
Preconditions.checkState(operands.size() == 2, "Expected 2 operands, got[%s]", operands.size());
boolean flip = false;
RexNode lhs = operands.get(0);
RexNode rhs = operands.get(1);
if (lhs.getKind() == SqlKind.LITERAL && rhs.getKind() != SqlKind.LITERAL) {
// swap lhs, rhs
RexNode x = lhs;
lhs = rhs;
rhs = x;
flip = true;
}
// Flip operator, maybe.
final SqlKind flippedKind;
if (flip) {
switch (kind) {
case EQUALS:
case NOT_EQUALS:
case IS_NOT_DISTINCT_FROM:
case IS_DISTINCT_FROM:
flippedKind = kind;
break;
case GREATER_THAN:
flippedKind = SqlKind.LESS_THAN;
break;
case GREATER_THAN_OR_EQUAL:
flippedKind = SqlKind.LESS_THAN_OR_EQUAL;
break;
case LESS_THAN:
flippedKind = SqlKind.GREATER_THAN;
break;
case LESS_THAN_OR_EQUAL:
flippedKind = SqlKind.GREATER_THAN_OR_EQUAL;
break;
default:
throw new ISE("Kind[%s] not expected here", kind);
}
} else {
flippedKind = kind;
}
final DruidExpression rhsExpression = toDruidExpression(plannerContext, rowSignature, rhs);
final Expr rhsParsed = rhsExpression != null
? plannerContext.parseExpression(rhsExpression.getExpression())
: null;
// rhs must be a literal
if (rhsParsed == null || !rhsParsed.isLiteral()) {
return null;
}
// Translate lhs to a DruidExpression.
final DruidExpression lhsExpression = toDruidExpression(plannerContext, rowSignature, lhs);
if (lhsExpression == null) {
return null;
}
// Special handling for filters like FLOOR(__time TO granularity).
final Granularity queryGranularity =
toQueryGranularity(lhsExpression, plannerContext.getExpressionParser());
if (queryGranularity != null && !RexLiteral.isNullLiteral(rhs)) {
// lhs is a time-floor expression; rhs must be a timestamp or millis
final long rhsMillis;
if (rhs.getType().getSqlTypeName() == SqlTypeName.BIGINT) {
rhsMillis = ((Number) RexLiteral.value(rhs)).longValue();
} else {
rhsMillis = Calcites.calciteDateTimeLiteralToJoda(rhs, plannerContext.getTimeZone()).getMillis();
}
return buildTimeFloorFilter(
ColumnHolder.TIME_COLUMN_NAME,
queryGranularity,
flippedKind,
rhsMillis,
plannerContext
);
}
String column;
final ExtractionFn extractionFn;
if (lhsExpression.isSimpleExtraction()) {
column = lhsExpression.getSimpleExtraction().getColumn();
extractionFn = lhsExpression.getSimpleExtraction().getExtractionFn();
} else if (virtualColumnRegistry != null) {
column = virtualColumnRegistry.getOrCreateVirtualColumnForExpression(
lhsExpression,
lhs.getType()
);
extractionFn = null;
} else {
return null;
}
if (column.equals(ColumnHolder.TIME_COLUMN_NAME) && extractionFn instanceof TimeFormatExtractionFn) {
// Check if we can strip the extractionFn and convert the filter to a direct filter on __time.
// This allows potential conversion to query-level "intervals" later on, which is ideal for Druid queries.
final Granularity granularity = ExtractionFns.toQueryGranularity(extractionFn);
if (granularity != null) {
// lhs is FLOOR(__time TO granularity); rhs must be a timestamp.
final long rhsMillis = Calcites.calciteDateTimeLiteralToJoda(rhs, plannerContext.getTimeZone()).getMillis();
return buildTimeFloorFilter(column, granularity, flippedKind, rhsMillis, plannerContext);
}
}
final ColumnType matchValueType = Calcites.getColumnTypeForRelDataType(rhs.getType());
if (plannerContext.isUseBoundsAndSelectors()) {
if (matchValueType == null || !matchValueType.isPrimitive()) {
// Fall back to expression filter.
return null;
}
final String stringVal;
if (rhsParsed.getLiteralValue() == null) {
stringVal = null;
} else if (RexUtil.isLiteral(rhs, true) && SqlTypeName.NUMERIC_TYPES.contains(rhs.getType().getSqlTypeName())) {
// Peek inside the original rhs for numerics, rather than using the parsed version, for highest fidelity
// to what the query originally contained. (It may be a BigDecimal.)
stringVal = String.valueOf(RexLiteral.value(rhs));
} else {
stringVal = String.valueOf(rhsParsed.getLiteralValue());
}
if (stringVal == null) {
// Fall back to expression filter.
return null;
}
// Numeric lhs needs a numeric comparison.
final StringComparator comparator = Calcites.getStringComparatorForRelDataType(lhs.getType());
if (comparator == null) {
// Type is not comparable.
return null;
}
final BoundRefKey boundRefKey = new BoundRefKey(column, extractionFn, comparator);
final DimFilter filter;
// Always use BoundDimFilters, to simplify filter optimization later (it helps to remember the comparator).
switch (flippedKind) {
case EQUALS:
case IS_NOT_DISTINCT_FROM:
// OK to treat EQUALS, IS_NOT_DISTINCT_FROM the same since we know stringVal is nonnull.
filter = Bounds.equalTo(boundRefKey, stringVal);
break;
case NOT_EQUALS:
case IS_DISTINCT_FROM:
// OK to treat NOT_EQUALS, IS_DISTINCT_FROM the same since we know stringVal is nonnull.
filter = new NotDimFilter(Bounds.equalTo(boundRefKey, stringVal));
break;
case GREATER_THAN:
filter = Bounds.greaterThan(boundRefKey, stringVal);
break;
case GREATER_THAN_OR_EQUAL:
filter = Bounds.greaterThanOrEqualTo(boundRefKey, stringVal);
break;
case LESS_THAN:
filter = Bounds.lessThan(boundRefKey, stringVal);
break;
case LESS_THAN_OR_EQUAL:
filter = Bounds.lessThanOrEqualTo(boundRefKey, stringVal);
break;
default:
throw new IllegalStateException("Shouldn't have got here");
}
return filter;
} else {
final Object val = rhsParsed.getLiteralValue();
if (val == null) {
// fall back to expression filter
return null;
}
// extractionFn are not supported by equality/range filter
if (extractionFn != null) {
if (virtualColumnRegistry != null) {
column = virtualColumnRegistry.getOrCreateVirtualColumnForExpression(
lhsExpression,
lhs.getType()
);
} else {
// if this happens for some reason, bail and use an expression filter
return null;
}
}
final RangeRefKey rangeRefKey = new RangeRefKey(column, matchValueType);
final DimFilter filter;
// Always use RangeFilter, to simplify filter optimization later
switch (flippedKind) {
case EQUALS:
case IS_NOT_DISTINCT_FROM:
filter = Ranges.equalTo(rangeRefKey, val);
break;
case NOT_EQUALS:
case IS_DISTINCT_FROM:
filter = new NotDimFilter(Ranges.equalTo(rangeRefKey, val));
break;
case GREATER_THAN:
filter = Ranges.greaterThan(rangeRefKey, val);
break;
case GREATER_THAN_OR_EQUAL:
filter = Ranges.greaterThanOrEqualTo(rangeRefKey, val);
break;
case LESS_THAN:
filter = Ranges.lessThan(rangeRefKey, val);
break;
case LESS_THAN_OR_EQUAL:
filter = Ranges.lessThanOrEqualTo(rangeRefKey, val);
break;
default:
throw new IllegalStateException("Shouldn't have got here");
}
return filter;
}
} else if (rexNode instanceof RexCall) {
final SqlOperator operator = ((RexCall) rexNode).getOperator();
final SqlOperatorConversion conversion = plannerContext.getPlannerToolbox()
.operatorTable()
.lookupOperatorConversion(operator);
if (conversion == null) {
return null;
} else {
return conversion.toDruidFilter(plannerContext, rowSignature, virtualColumnRegistry, rexNode);
}
} else {
return null;
}
}