private static DimFilter toSimpleLeafFilter()

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