diff --git a/core/src/main/java/org/apache/calcite/rex/RexInterpreter.java b/core/src/main/java/org/apache/calcite/rex/RexInterpreter.java index 9e2bd0e3a723..7a39c38b72af 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexInterpreter.java +++ b/core/src/main/java/org/apache/calcite/rex/RexInterpreter.java @@ -368,6 +368,14 @@ private static Comparable ceil(RexCall call, List values) { if (values.get(0) == N) { return N; } + if (values.size() == 1) { + if (values.get(0) instanceof BigDecimal) { + BigDecimal bigDecimal = (BigDecimal) values.get(0); + return bigDecimal.longValue(); + } else { + return values.get(0); + } + } final Long v = (Long) values.get(0); final TimeUnitRange unit = (TimeUnitRange) values.get(1); switch (unit) { diff --git a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java index 4710bb1f2438..edf715bd81b1 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java +++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java @@ -88,6 +88,14 @@ public class RexSimplify { private static final Strong STRONG = new Strong(); + private static final ImmutableList IDEMOTENT_UNARY_FUNCTIONS = + ImmutableList.of(SqlStdOperatorTable.UPPER, + SqlStdOperatorTable.LOWER, + SqlStdOperatorTable.INITCAP, + SqlStdOperatorTable.ABS, + SqlStdOperatorTable.FLOOR, + SqlStdOperatorTable.CEIL); + /** * Creates a RexSimplify. * @@ -333,7 +341,8 @@ RexNode simplify(RexNode e, RexUnknownAs unknownAs) { return simplifyM2v((RexCall) e); default: if (e.getClass() == RexCall.class) { - return simplifyGenericNode((RexCall) e); + RexCall rexCall = (RexCall) e; + return simplifyIdempotentUnaryFunction(rexCall); } else { return e; } @@ -397,6 +406,30 @@ RexNode isFalse(RexNode e) { : rexBuilder.makeCall(SqlStdOperatorTable.IS_FALSE, e); } + /** + * Runs simplification unary function by eliminating idempotent. + * + *

Examples: + *

+ */ + private RexNode simplifyIdempotentUnaryFunction(RexCall rexCall) { + while (IDEMOTENT_UNARY_FUNCTIONS.contains(rexCall.getOperator()) + && rexCall.getOperands().get(0) instanceof RexCall) { + RexCall subRexCall = (RexCall) rexCall.getOperands().get(0); + if (rexCall.getOperator() == subRexCall.getOperator() + && rexCall.operands.size() == subRexCall.operands.size()) { + rexCall = subRexCall; + } else { + break; + } + } + return simplifyGenericNode(rexCall); + } + /** * Runs simplification inside a non-specialized node. */ @@ -2373,7 +2406,7 @@ && sameTypeOrNarrowsNullability(e.getType(), intExpr.getType())) { private RexNode simplifyCeilFloor(RexCall e) { if (e.getOperands().size() != 2) { // Bail out since we only simplify floor - return e; + return simplifyIdempotentUnaryFunction(e); } final RexNode operand = simplify(e.getOperands().get(0), UNKNOWN); if (e.getKind() == operand.getKind()) { @@ -2382,7 +2415,7 @@ private RexNode simplifyCeilFloor(RexCall e) { final RexCall child = (RexCall) operand; if (child.getOperands().size() != 2) { // Bail out since we only simplify ceil/floor - return e; + return simplifyIdempotentUnaryFunction(e); } final RexLiteral parentFlag = (RexLiteral) e.operands.get(1); final TimeUnitRange parentFlagValue = (TimeUnitRange) parentFlag.getValue(); @@ -2391,12 +2424,12 @@ private RexNode simplifyCeilFloor(RexCall e) { if (parentFlagValue != null && childFlagValue != null) { if (canRollUp(parentFlagValue.startUnit, childFlagValue.startUnit)) { return e.clone(e.getType(), - ImmutableList.of(child.getOperands().get(0), parentFlag)); + ImmutableList.of(simplify(child.getOperands().get(0)), parentFlag)); } } } return e.clone(e.getType(), - ImmutableList.of(operand, e.getOperands().get(1))); + ImmutableList.of(simplify(operand), e.getOperands().get(1))); } /** Simplify TRIM function by eliminating nested duplication. diff --git a/core/src/test/java/org/apache/calcite/test/RexImplicationCheckerTest.java b/core/src/test/java/org/apache/calcite/test/RexImplicationCheckerTest.java index 042dcae1f0a9..cb481ac8c485 100644 --- a/core/src/test/java/org/apache/calcite/test/RexImplicationCheckerTest.java +++ b/core/src/test/java/org/apache/calcite/test/RexImplicationCheckerTest.java @@ -24,6 +24,7 @@ import org.apache.calcite.rex.RexSimplify; import org.apache.calcite.rex.RexUnknownAs; import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.fun.SqlStdOperatorTable; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.util.DateString; @@ -35,9 +36,13 @@ import org.junit.jupiter.api.Test; +import java.util.Arrays; +import java.util.List; + import static org.apache.calcite.test.RexImplicationCheckerFixtures.Fixture; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasToString; @@ -361,6 +366,89 @@ public class RexImplicationCheckerTest { hasToString("2014")); } + /** Test case for + * [CALCITE-7122] + * Eliminate nested calls for idempotent unary functions UPPER/LOWER/ABS/INITCAP/CEIL/FLOOR. + * */ + @Test void testSimplifyIdempotentUnaryFunctions() { + // Test that: + // upper(upper(x)) is simplied to upper(x) + // lower(lower(x)) is simplied to lower(x) + // initcap(initcap(x)) is simplied to initcap(x) + final Fixture f = new Fixture(); + List testOeratorList = + Arrays.asList(SqlStdOperatorTable.UPPER, + SqlStdOperatorTable.LOWER, + SqlStdOperatorTable.INITCAP); + for (SqlOperator operator : testOeratorList) { + RexCall innerCall = + (RexCall) f.rexBuilder + .makeCall(operator, f.rexBuilder.makeLiteral("Calcite Test")); + RexCall outerCall = + (RexCall) f.rexBuilder.makeCall(operator, innerCall); + RexCall simplifiedInnerCall = + (RexCall) f.simplify.simplifyPreservingType(outerCall, + RexUnknownAs.UNKNOWN, true); + + assertThat(((RexLiteral) simplifiedInnerCall.getOperands().get(0)) + .getValue(), + is(((RexLiteral) innerCall.getOperands().get(0)).getValue())); + } + + // Test that: + // floor(floor(x)) is simplied to floor(x) + // ceil(ceil(x)) is simplied to ceil(x) + // abs(abs(x)) is simplied to abs(x) + testOeratorList = + Arrays.asList(SqlStdOperatorTable.ABS, + SqlStdOperatorTable.FLOOR, + SqlStdOperatorTable.CEIL); + for (SqlOperator operator : testOeratorList) { + RelDataType intType = f.rexBuilder.getTypeFactory().createSqlType(SqlTypeName.DECIMAL); + RexCall innerCall = + (RexCall) f.rexBuilder + .makeCall(operator, f.rexBuilder.makeLiteral(12, intType)); + RexCall outerCall = + (RexCall) f.rexBuilder + .makeCall(operator, innerCall); + RexCall simplifiedInnerCall = + (RexCall) f.simplify.simplifyPreservingType(outerCall, + RexUnknownAs.UNKNOWN, true); + assertThat(((RexLiteral) simplifiedInnerCall.getOperands().get(0)).getValue(), + is(((RexLiteral) innerCall.getOperands().get(0)).getValue())); + } + + // Test that max(abs(abs(x))) is simplied to max(abs(x)) + RelDataType intType = f.rexBuilder.getTypeFactory().createSqlType(SqlTypeName.INTEGER); + RexCall innerCall = + (RexCall) f.rexBuilder + .makeCall(SqlStdOperatorTable.ABS, f.rexBuilder.makeLiteral(12, intType)); + RexCall outerCall = + (RexCall) f.rexBuilder + .makeCall(SqlStdOperatorTable.ABS, innerCall); + RexCall maxCall = + (RexCall) f.rexBuilder + .makeCall(SqlStdOperatorTable.MAX, outerCall); + RexCall simplifiedInnerCall = + (RexCall) f.simplify.simplifyPreservingType(maxCall, + RexUnknownAs.UNKNOWN, true); + assertThat(simplifiedInnerCall.getOperator(), is(SqlStdOperatorTable.MAX)); + assertThat(simplifiedInnerCall.getOperands().get(0), is(innerCall)); + assertThat(simplifiedInnerCall.getOperands().get(0), not(outerCall)); + + // negative test + innerCall = (RexCall) f.rexBuilder + .makeCall(SqlStdOperatorTable.UPPER, f.rexBuilder.makeLiteral("Calcite Test")); + outerCall = (RexCall) f.rexBuilder.makeCall(SqlStdOperatorTable.LOWER, innerCall); + simplifiedInnerCall = (RexCall) f.simplify + .simplifyPreservingType(outerCall, RexUnknownAs.UNKNOWN, true); + + assertThat(simplifiedInnerCall.getOperands().get(0), + not(((RexLiteral) innerCall.getOperands().get(0)).getValue())); + assertThat(((RexCall) simplifiedInnerCall.getOperands().get(0)).getOperands().get(0), + is(innerCall.getOperands().get(0))); + } + /** Test case for * [CALCITE-7042] * Eliminate nested TRIM calls, exploiting the fact that TRIM is idempotent. */