Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions core/src/main/java/org/apache/calcite/rex/RexSimplify.java
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,11 @@ RexNode simplify(RexNode e, RexUnknownAs unknownAs) {
}
return rexBuilder.makeNullLiteral(e.getType());
}

if (e instanceof RexCall) {
e = simplifyIdempotentUnaryFunction((RexCall) e);
}

switch (e.getKind()) {
case AND:
return simplifyAnd((RexCall) e, unknownAs);
Expand Down Expand Up @@ -342,6 +347,33 @@ RexNode simplify(RexNode e, RexUnknownAs unknownAs) {
}
}

/**
* Runs simplification unary function by eliminating idempotent.
*
* <p>Examples:
* <ul>
* <li>{@code abs(abs(abs(n))} returns {@code abs(n)}
* </ul>
*/
private RexNode simplifyIdempotentUnaryFunction(RexCall rexCall) {
while (true) {
if (!rexCall.getOperator().isIdempotent()) {
break;
}
if (!(rexCall.getOperands().get(0) instanceof RexCall)) {
break;
}
RexCall subRexCall = (RexCall) rexCall.getOperands().get(0);
if (rexCall.getOperator() == subRexCall.getOperator()
&& rexCall.operands.size() == subRexCall.operands.size()) {
rexCall = subRexCall;
} else {
break;
}
}
return rexCall;
}

/** Applies NOT to an expression. */
RexNode not(RexNode e) {
return RexUtil.not(rexBuilder, e);
Expand Down
62 changes: 49 additions & 13 deletions core/src/main/java/org/apache/calcite/sql/SqlBasicFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public final class SqlBasicFunction extends SqlFunction {
private final int callValidator;
private final Function<SqlOperatorBinding, SqlMonotonicity> monotonicityInference;
private final boolean dynamic;
private final boolean idempotent;

//~ Constructors -----------------------------------------------------------

Expand All @@ -75,7 +76,8 @@ private SqlBasicFunction(String name, SqlKind kind, SqlSyntax syntax,
Integer callValidator,
SqlFunctionCategory category,
Function<SqlOperatorBinding, SqlMonotonicity> monotonicityInference,
boolean dynamic) {
boolean dynamic,
boolean idempotent) {
super(name, kind,
requireNonNull(returnTypeInference, "returnTypeInference"),
operandTypeInference,
Expand All @@ -87,6 +89,35 @@ private SqlBasicFunction(String name, SqlKind kind, SqlSyntax syntax,
this.monotonicityInference =
requireNonNull(monotonicityInference, "monotonicityInference");
this.dynamic = dynamic;
this.idempotent = idempotent;
}

/** Creates a new SqlFunction for a call to a built-in function. */
private SqlBasicFunction(String name, SqlKind kind, SqlSyntax syntax,
boolean deterministic, SqlReturnTypeInference returnTypeInference,
@Nullable SqlOperandTypeInference operandTypeInference,
SqlOperandHandler operandHandler,
SqlOperandTypeChecker operandTypeChecker,
Integer callValidator,
SqlFunctionCategory category,
Function<SqlOperatorBinding, SqlMonotonicity> monotonicityInference,
boolean dynamic) {
this(name, kind, syntax, deterministic,
returnTypeInference, operandTypeInference, operandHandler,
operandTypeChecker, callValidator, category, monotonicityInference,
dynamic, false);
}

/** Creates a {@code SqlBasicFunction} with idempotent property. */
public static SqlBasicFunction create(String name,
SqlReturnTypeInference returnTypeInference,
SqlOperandTypeChecker operandTypeChecker,
SqlFunctionCategory category,
boolean idempotent) {
return new SqlBasicFunction(name, SqlKind.OTHER_FUNCTION,
SqlSyntax.FUNCTION, true, returnTypeInference, null,
OperandHandlers.DEFAULT, operandTypeChecker, 0,
category, call -> SqlMonotonicity.NOT_MONOTONIC, false, idempotent);
}

/**
Expand Down Expand Up @@ -180,35 +211,40 @@ public static SqlBasicFunction create(String name,
return dynamic;
}

@Override public boolean isIdempotent() {
return idempotent;
}

/** Returns a copy of this function with a given name. */
public SqlBasicFunction withName(String name) {
return new SqlBasicFunction(name, kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}

/** Returns a copy of this function with a given kind. */
public SqlBasicFunction withKind(SqlKind kind) {
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}

/** Returns a copy of this function with a given category. */
public SqlBasicFunction withFunctionType(SqlFunctionCategory category) {
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator, category, monotonicityInference, dynamic);
getOperandTypeChecker(), callValidator, category, monotonicityInference,
dynamic, idempotent);
}

/** Returns a copy of this function with a given syntax. */
public SqlBasicFunction withSyntax(SqlSyntax syntax) {
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}

/** Returns a copy of this function with a given strategy for inferring
Expand All @@ -218,7 +254,7 @@ public SqlBasicFunction withReturnTypeInference(
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
returnTypeInference, getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}

/** Returns a copy of this function with a given strategy for inferring
Expand All @@ -228,7 +264,7 @@ public SqlBasicFunction withOperandTypeInference(
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), operandTypeInference, operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}

/** Returns a copy of this function with a given strategy for handling
Expand All @@ -237,14 +273,14 @@ public SqlBasicFunction withOperandHandler(SqlOperandHandler operandHandler) {
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}
/** Returns a copy of this function with a given determinism. */
public SqlBasicFunction withDeterministic(boolean deterministic) {
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}

/** Returns a copy of this function with a given strategy for inferring
Expand All @@ -254,27 +290,27 @@ public SqlBasicFunction withMonotonicityInference(
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}

public SqlBasicFunction withValidation(int callValidator) {
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}

public SqlBasicFunction withDynamic(boolean dynamic) {
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
getOperandTypeChecker(), callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}

public SqlBasicFunction withOperandTypeChecker(SqlOperandTypeChecker operandTypeChecker) {
return new SqlBasicFunction(getName(), kind, syntax, deterministic,
getReturnTypeInference(), getOperandTypeInference(), operandHandler,
operandTypeChecker, callValidator,
getFunctionType(), monotonicityInference, dynamic);
getFunctionType(), monotonicityInference, dynamic, idempotent);
}
}
4 changes: 4 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/SqlOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -1137,4 +1137,8 @@ public boolean requiresDecimalExpansion() {
public boolean argumentMustBeScalar(int ordinal) {
return true;
}

public boolean isIdempotent() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1824,19 +1824,22 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
SqlBasicFunction.create("UPPER",
ReturnTypes.ARG0_NULLABLE,
OperandTypes.CHARACTER,
SqlFunctionCategory.STRING);
SqlFunctionCategory.STRING,
true);

public static final SqlFunction LOWER =
SqlBasicFunction.create("LOWER",
ReturnTypes.ARG0_NULLABLE,
OperandTypes.CHARACTER,
SqlFunctionCategory.STRING);
SqlFunctionCategory.STRING,
true);

public static final SqlFunction INITCAP =
SqlBasicFunction.create("INITCAP",
ReturnTypes.ARG0_NULLABLE,
OperandTypes.CHARACTER,
SqlFunctionCategory.STRING);
SqlFunctionCategory.STRING,
true);

public static final SqlFunction ASCII =
SqlBasicFunction.create("ASCII",
Expand Down Expand Up @@ -1895,7 +1898,8 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
SqlBasicFunction.create("ABS",
ReturnTypes.ARG0,
OperandTypes.NUMERIC_OR_INTERVAL,
SqlFunctionCategory.NUMERIC);
SqlFunctionCategory.NUMERIC,
true);

/** The {@code ACOS(numeric)} function. */
public static final SqlFunction ACOS =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8939,6 +8939,29 @@ private void checkLiteral2(String expression, String expected) {
.withCalcite().ok(expectedCalciteX);
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-7122">[CALCITE-7122]
* Eliminate nested calls for idempotent unary functions UPPER/LOWER/ABS/INITCAP</a>. */
@Test void testSimplifyIdempotentUnaryFunctions() {
Map<String, String> sqls = ImmutableMap.<String, String>builder()
.put("select abs(abs(5))",
"SELECT ABS(5)\nFROM (VALUES (0)) AS \"t\" (\"ZERO\")")
.put("select upper(upper('Abc'))",
"SELECT UPPER('Abc')\nFROM (VALUES (0)) AS \"t\" (\"ZERO\")")
.put("select lower(lower('Abc'))",
"SELECT LOWER('Abc')\nFROM (VALUES (0)) AS \"t\" (\"ZERO\")")
.put("select initcap(initcap('Abc'))",
"SELECT INITCAP('Abc')\nFROM (VALUES (0)) AS \"t\" (\"ZERO\")")
.put("select max(abs(abs(5)))",
"SELECT MAX(ABS(5))\nFROM (VALUES (0)) AS \"t\" (\"ZERO\")")
.put("select upper(lower('Abc'))",
"SELECT UPPER(LOWER('Abc'))\nFROM (VALUES (0)) AS \"t\" (\"ZERO\")")
.build();

sqls.forEach((sql, expected) ->
sql(sql).withCalcite().ok(expected));
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-5723">[CALCITE-5723]
* Oracle dialect generates SQL that cannot be recognized by lower version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,6 +36,8 @@

import org.junit.jupiter.api.Test;

import java.util.List;

import static org.apache.calcite.test.RexImplicationCheckerFixtures.Fixture;

import static org.hamcrest.CoreMatchers.is;
Expand Down Expand Up @@ -361,6 +364,53 @@ public class RexImplicationCheckerTest {
hasToString("2014"));
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-7122">[CALCITE-7122]
* Eliminate nested calls for idempotent unary functions UPPER/LOWER/ABS/INITCAP</a>. */
@Test void testSimplifyIdempotentUnaryFunctions() {
// Test that:
// upper(upper(x)) is simplified to upper(x)
// lower(lower(x)) is simplified to lower(x)
// initcap(initcap(x)) is simplified to initcap(x)
final Fixture f = new Fixture();
List<SqlOperator> testOeratorList =
ImmutableList.of(SqlStdOperatorTable.UPPER,
SqlStdOperatorTable.LOWER,
SqlStdOperatorTable.INITCAP);
for (SqlOperator operator : testOeratorList) {
RexNode innerCall = f.rexBuilder.makeCall(operator, f.rexBuilder.makeLiteral("Calcite"));
RexNode outerCall = f.rexBuilder.makeCall(operator, innerCall);
RexNode simplifiedCall =
f.simplify.simplifyPreservingType(outerCall, RexUnknownAs.UNKNOWN, true);
assertThat(innerCall.equals(simplifiedCall), is(true));
}

// Test that:
// abs(abs(x)) is simplified to abs(x)
RelDataType intType = f.rexBuilder.getTypeFactory().createSqlType(SqlTypeName.INTEGER);
RexNode abs =
f.rexBuilder.makeCall(SqlStdOperatorTable.ABS, f.rexBuilder.makeLiteral(12, intType));
RexNode absAbs = f.rexBuilder.makeCall(SqlStdOperatorTable.ABS, abs);
RexNode simplifiedAbs =
f.simplify.simplifyPreservingType(absAbs, RexUnknownAs.UNKNOWN, true);
assertThat(abs, is(simplifiedAbs));

// Test that max(abs(abs(x))) is simplified to max(abs(x))
RexNode maxAbs = f.rexBuilder.makeCall(SqlStdOperatorTable.MAX, abs);
RexNode maxAbsAbs = f.rexBuilder.makeCall(SqlStdOperatorTable.MAX, absAbs);
RexNode simplifiedMaxAbsAbs =
f.simplify.simplifyPreservingType(maxAbsAbs, RexUnknownAs.UNKNOWN, true);
assertThat(maxAbs.equals(simplifiedMaxAbsAbs), is(true));

// lower(upper(x)) is not simplified
RexNode upper = f.rexBuilder
.makeCall(SqlStdOperatorTable.UPPER, f.rexBuilder.makeLiteral("Calcite"));
RexNode lowerUpper = f.rexBuilder.makeCall(SqlStdOperatorTable.LOWER, upper);
RexNode simplifiedLowerUpper =
f.simplify.simplifyPreservingType(lowerUpper, RexUnknownAs.UNKNOWN, true);
assertThat(lowerUpper.equals(simplifiedLowerUpper), is(true));
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-7042">[CALCITE-7042]
* Eliminate nested TRIM calls, exploiting the fact that TRIM is idempotent</a>. */
Expand Down
Loading