twdsilva commented on code in PR #2876:
URL: https://github.com/apache/calcite/pull/2876#discussion_r1038665587


##########
core/src/main/java/org/apache/calcite/rel/rules/materialize/MaterializedViewRule.java:
##########
@@ -1252,6 +1285,193 @@ protected RexNode replaceWithOriginalReferences(final 
RexBuilder rexBuilder,
     }
   }
 
+  /**
+   * Used to generate a view predicate that is added to a materialized view 
that aggregates
+   * over a FLOOR(datetime) when the query has a range predicate on the same 
column.
+   */
+  static class ImplicitViewPredicateShuttle extends RexShuttle {
+
+    private final RexBuilder rexBuilder;
+    private final RexCall floorCall;
+    private final RexInputRef rexInputRef;
+    private final boolean generateViewFilter;
+    private long lowerBound;
+    private long upperBound;
+
+    ImplicitViewPredicateShuttle(RexBuilder rexBuilder, RexCall floorCall,
+        RexInputRef rexInputRef,
+        boolean generateViewFilter) {
+      this.floorCall = floorCall;
+      this.rexBuilder = rexBuilder;
+      this.rexInputRef = rexInputRef;
+      this.generateViewFilter = generateViewFilter;
+    }
+
+    private RexNode transformCall(RexCall call, boolean isLowerBound) {
+      SqlOperator transformedCallOperator = isLowerBound
+          ? SqlStdOperatorTable.GREATER_THAN_OR_EQUAL : 
SqlStdOperatorTable.LESS_THAN;
+      // matches functions of the form x > 5 or 5 > x
+      RexNode literalOperand = call.operands.get(0);
+      RexNode tableInputRefOperand = call.operands.get(1);
+      final int floorIndex = ((RexInputRef) 
floorCall.getOperands().get(0)).getIndex();
+      boolean reverseOperands = false;
+      if ((literalOperand.getKind() == SqlKind.LITERAL
+          && tableInputRefOperand.getKind() == SqlKind.TABLE_INPUT_REF)
+          || (literalOperand.getKind() == SqlKind.TABLE_INPUT_REF
+          && tableInputRefOperand.getKind() == SqlKind.LITERAL)) {
+        if (literalOperand.getKind() == SqlKind.TABLE_INPUT_REF
+            && tableInputRefOperand.getKind() == SqlKind.LITERAL) {
+          literalOperand = call.operands.get(1);
+          tableInputRefOperand = call.operands.get(0);
+          reverseOperands = true;
+        }
+        int predicateIndex = ((RexTableInputRef) 
tableInputRefOperand).getIndex();
+        if (floorIndex == predicateIndex) {
+          // if the query predicate contains a range over the column that is 
floored in the
+          // materialized view we can generate a filter on the view
+          boolean shiftTruncatedVal = call.getOperator() != 
transformedCallOperator;
+          long truncatedVal = getModifiedVal(shiftTruncatedVal, isLowerBound, 
literalOperand);
+          RexNode truncatedLiteral =
+              
rexBuilder.makeTimestampLiteral(TimestampString.fromMillisSinceEpoch(truncatedVal),
+                  0);
+          if (isLowerBound) {
+            lowerBound = truncatedVal;
+          } else {
+            upperBound = truncatedVal;
+          }
+          if (generateViewFilter) {
+            tableInputRefOperand = rexInputRef;
+          }
+          RexNode modifiedCall = rexBuilder.makeCall(call.getType(), 
transformedCallOperator,
+              reverseOperands ? ImmutableList.of(tableInputRefOperand, 
truncatedLiteral)
+                  : ImmutableList.of(truncatedLiteral, tableInputRefOperand));
+          return modifiedCall;
+        } else {
+          // ignore predicates on different columns
+          return rexBuilder.makeLiteral(true);
+        }
+      }
+      return super.visitCall(call);
+    }
+
+    private long getModifiedVal(boolean shiftModifiedVal, boolean isLowerBound,
+        RexNode literalOperand) {
+      SqlOperator floorOperator = isLowerBound ? SqlStdOperatorTable.CEIL
+          : SqlStdOperatorTable.FLOOR;
+      RexNode truncatedLiteral = rexBuilder.makeCall(floorCall.getType(),
+          floorOperator, ImmutableList.of(literalOperand, 
floorCall.getOperands().get(1)));
+      Comparable v0 = RexInterpreter.evaluate(truncatedLiteral, 
Collections.emptyMap());
+      if (v0 == null) {
+        throw new AssertionError("interpreter returned null for " + v0);
+      }
+      Comparable v1 = RexInterpreter.evaluate(literalOperand, 
Collections.emptyMap());
+      if (v1 == null) {
+        throw new AssertionError("interpreter returned null for " + v1);
+      }
+      long modifiedVal = (long) v0;
+      final long originalVal = (long) v1;
+      // Since the view contains a FLOOR() if the query does a > or <= and the 
operand is already
+      // a FLOORED value we have to shift the value to the next higher or 
lower floored value
+      // If the query contains a >= or < we don't need to shift the modified 
value
+      if (shiftModifiedVal && modifiedVal == originalVal) {
+        final RexLiteral literal = (RexLiteral) floorCall.getOperands().get(1);
+        final TimeUnitRange unit = 
castNonNull(literal.getValueAs(TimeUnitRange.class));
+        if (isLowerBound) {
+          modifiedVal += unit.startUnit.multiplier.longValue();
+        } else {
+          modifiedVal -= unit.startUnit.multiplier.longValue();
+        }
+      }
+      return modifiedVal;
+    }
+
+    /**
+     * Generates a predicate that is added to the view so that we can use 
union rewriting.
+     *
+     * @param call the query predicate
+     * @return the predicate that is to be used as the view predicate or a 
view filter
+     */
+    @SuppressWarnings("BetaApi")
+    @Override public RexNode visitCall(RexCall call) {
+      switch (call.getKind()) {
+      case SEARCH:
+        Sarg sarg = castNonNull(((RexLiteral) 
call.getOperands().get(1)).getValueAs(Sarg.class));
+        if (sarg.rangeSet.asRanges().size() != 1) {
+          return super.visitCall(call);
+        }
+        Range r = (Range) sarg.rangeSet.asRanges().iterator().next();
+        final int floorIndex = ((RexInputRef) 
floorCall.getOperands().get(0)).getIndex();
+        RexNode tableInputRefOperand = call.operands.get(0);
+        if (!(tableInputRefOperand instanceof RexTableInputRef)) {
+          // ignore SARG that isn't on table column
+          return rexBuilder.makeLiteral(true);
+        }
+        int predicateIndex = ((RexTableInputRef) 
tableInputRefOperand).getIndex();
+        if (floorIndex != predicateIndex) {
+          // ignore predicates on different columns
+          return rexBuilder.makeLiteral(true);
+        }
+        if (!(r.lowerEndpoint() instanceof TimestampString)
+            || !(r.upperEndpoint() instanceof TimestampString)) {
+          // ignore predicates where the type isn't a timestamp
+          return rexBuilder.makeLiteral(true);
+        }
+        RexNode lb = rexBuilder.makeTimestampLiteral((TimestampString) 
r.lowerEndpoint(), 0);
+        // if the lower bound is exclusive we need to shift the modified value 
to the next higher
+        // floored value
+        long modifiedLbVal = getModifiedVal(r.lowerBoundType() == 
BoundType.OPEN, true, lb);
+        TimestampString lbTimestampString = 
TimestampString.fromMillisSinceEpoch(modifiedLbVal);
+        RexNode ub = rexBuilder.makeTimestampLiteral((TimestampString) 
r.upperEndpoint(), 0);
+        // if the upper bound is inclusive we need to shift the modified value 
to the previous lower
+        // floored value
+        long modifiedUbVal = getModifiedVal(r.upperBoundType() == 
BoundType.CLOSED, false, ub);
+        TimestampString ubTimestampString = 
TimestampString.fromMillisSinceEpoch(modifiedUbVal);
+
+        lowerBound = modifiedLbVal;
+        upperBound = modifiedUbVal;
+
+        if (generateViewFilter) {
+          tableInputRefOperand = rexInputRef;
+        }
+
+        RelDataType type = call.getOperands().get(1).getType();
+        Sarg modSarg = Sarg.of(RexUnknownAs.UNKNOWN,
+            ImmutableRangeSet.of(Range.closedOpen(lbTimestampString, 
ubTimestampString)));
+        RexNode searchArgumentLiteral = 
rexBuilder.makeSearchArgumentLiteral(modSarg, type);
+        RexNode res = rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, 
tableInputRefOperand,
+            searchArgumentLiteral);
+        return res;
+      // Since the view contains a FLOOR() we use GREATER_THAN_OR_EQUAL for 
the lower bound
+      // range comparison
+      case GREATER_THAN_OR_EQUAL:
+      case GREATER_THAN:
+        // CEIL is used to determine the lower bound range that for the view 
predicate
+        // For eg if the query has (date > TIMESTAMP'2018-01-01 01:00:00') and 
we have a view
+        // that rolls up to the minute we generate a predicate (date >= 
TIMESTAMP'2018-01-01
+        // 02:00:00')
+        return transformCall(call, true);
+      // since the view contains a FLOOR() we use LESS_THAN for the upper bound
+      // range comparison
+      case LESS_THAN:
+      case LESS_THAN_OR_EQUAL:
+        // FLOOR is used to determine the upper bound range that for the view 
predicate
+        // For eg if the query has (date <= TIMESTAMP'2018-01-01 05:00:00') 
and we have a view
+        // that rolls up to the minute we generate a predicate (date < 
TIMESTAMP'2018-01-01
+        // 04:00:00')
+        return transformCall(call, false);
+      case EQUALS:
+        return rexBuilder.makeLiteral(true);
+      default:
+        return super.visitCall(call);
+      }
+    }
+
+    public boolean isRangeMatched() {
+      return lowerBound != 0L || upperBound != 0L && lowerBound != upperBound;

Review Comment:
   fixed



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscr...@calcite.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to