[CALCITE-1668] Simplify "1 = 1" to true, "1 > 2" to false (Kevin Risden)
Close apache/calcite#387 Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/255cd96a Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/255cd96a Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/255cd96a Branch: refs/heads/master Commit: 255cd96a79eb68fcf852b8e7af6299d7dfb356c7 Parents: 8df27ce Author: Kevin Risden <[email protected]> Authored: Sun Feb 19 15:56:35 2017 -0600 Committer: Julian Hyde <[email protected]> Committed: Fri Mar 3 12:00:05 2017 -0800 ---------------------------------------------------------------------- .../java/org/apache/calcite/rex/RexUtil.java | 81 +++++++++++++++----- .../org/apache/calcite/test/RelBuilderTest.java | 14 ++++ .../org/apache/calcite/test/RexProgramTest.java | 65 ++++++++++++++++ 3 files changed, 140 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/255cd96a/core/src/main/java/org/apache/calcite/rex/RexUtil.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java b/core/src/main/java/org/apache/calcite/rex/RexUtil.java index a6b9e4e..fd60098 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java +++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java @@ -1641,33 +1641,74 @@ public class RexUtil { case LESS_THAN: case LESS_THAN_OR_EQUAL: case NOT_EQUALS: - final List<RexNode> operands = ((RexCall) e).getOperands(); - if (RexUtil.eq(operands.get(0), operands.get(1)) - && (unknownAsFalse - || (!operands.get(0).getType().isNullable() - && !operands.get(1).getType().isNullable()))) { - switch (e.getKind()) { - case EQUALS: - case GREATER_THAN_OR_EQUAL: - case LESS_THAN_OR_EQUAL: - // "x = x" simplifies to "x is not null" (similarly <= and >=) - return simplify(rexBuilder, - rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, - operands.get(0))); - default: - // "x != x" simplifies to "false" (similarly < and >) - return rexBuilder.makeLiteral(false); - } - } - return simplifyCall(rexBuilder, (RexCall) e); + return simplifyComparison(rexBuilder, (RexCall) e, unknownAsFalse); default: return e; } } - private static RexNode simplifyCall(RexBuilder rexBuilder, RexCall e) { + // e must be a comparison (=, >, >=, <, <=, !=) + private static RexNode simplifyComparison(RexBuilder rexBuilder, RexCall e, + boolean unknownAsFalse) { final List<RexNode> operands = new ArrayList<>(e.operands); simplifyList(rexBuilder, operands); + + // Simplify "x <op> x" + final RexNode o0 = operands.get(0); + final RexNode o1 = operands.get(1); + if (RexUtil.eq(o0, o1) + && (unknownAsFalse + || (!o0.getType().isNullable() + && !o1.getType().isNullable()))) { + switch (e.getKind()) { + case EQUALS: + case GREATER_THAN_OR_EQUAL: + case LESS_THAN_OR_EQUAL: + // "x = x" simplifies to "x is not null" (similarly <= and >=) + return simplify(rexBuilder, + rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, o0)); + default: + // "x != x" simplifies to "false" (similarly < and >) + return rexBuilder.makeLiteral(false); + } + } + + // Simplify "<literal1> <op> <literal2>" + // For example, "1 = 2" becomes FALSE; + // "1 != 1" becomes FALSE; + // "1 != NULL" becomes UNKNOWN (or FALSE if unknownAsFalse); + // "1 != '1'" is unchanged because the types are not the same. + if (o0.isA(SqlKind.LITERAL) + && o1.isA(SqlKind.LITERAL) + && o0.getType().equals(o1.getType())) { + final Comparable v0 = ((RexLiteral) o0).getValue(); + final Comparable v1 = ((RexLiteral) o1).getValue(); + if (v0 == null || v1 == null) { + return unknownAsFalse + ? rexBuilder.makeLiteral(false) + : rexBuilder.makeNullLiteral(e.getType()); + } + @SuppressWarnings("unchecked") + final int comparisonResult = v0.compareTo(v1); + switch (e.getKind()) { + case EQUALS: + return rexBuilder.makeLiteral(comparisonResult == 0); + case GREATER_THAN: + return rexBuilder.makeLiteral(comparisonResult > 0); + case GREATER_THAN_OR_EQUAL: + return rexBuilder.makeLiteral(comparisonResult >= 0); + case LESS_THAN: + return rexBuilder.makeLiteral(comparisonResult < 0); + case LESS_THAN_OR_EQUAL: + return rexBuilder.makeLiteral(comparisonResult <= 0); + case NOT_EQUALS: + return rexBuilder.makeLiteral(comparisonResult != 0); + default: + throw new AssertionError(); + } + } + + // If none of the arguments were simplified, return the call unchanged. if (operands.equals(e.operands)) { return e; } http://git-wip-us.apache.org/repos/asf/calcite/blob/255cd96a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java index 842caf1..4eafeae 100644 --- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java +++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java @@ -208,6 +208,20 @@ public class RelBuilderTest { is("LogicalTableScan(table=[[scott, EMP]])\n")); } + @Test public void testScanFilterTriviallyFalse() { + // Equivalent SQL: + // SELECT * + // FROM emp + // WHERE 1 = 2 + final RelBuilder builder = RelBuilder.create(config().build()); + RelNode root = + builder.scan("EMP") + .filter(builder.equals(builder.literal(1), builder.literal(2))) + .build(); + assertThat(str(root), + is("LogicalValues(tuples=[[]])\n")); + } + @Test public void testScanFilterEquals() { // Equivalent SQL: // SELECT * http://git-wip-us.apache.org/repos/asf/calcite/blob/255cd96a/core/src/test/java/org/apache/calcite/test/RexProgramTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/RexProgramTest.java b/core/src/test/java/org/apache/calcite/test/RexProgramTest.java index 9ce8017..c1f567c 100644 --- a/core/src/test/java/org/apache/calcite/test/RexProgramTest.java +++ b/core/src/test/java/org/apache/calcite/test/RexProgramTest.java @@ -1308,6 +1308,71 @@ public class RexProgramTest { "1970-01-01 00:00:00"); // different from Hive } + @Test public void testSimplifyLiterals() { + final RexLiteral literalAbc = rexBuilder.makeLiteral("abc"); + final RexLiteral literalDef = rexBuilder.makeLiteral("def"); + + final RexLiteral literalZero = rexBuilder.makeExactLiteral(BigDecimal.ZERO); + final RexLiteral literalOne = rexBuilder.makeExactLiteral(BigDecimal.ONE); + final RexLiteral literalOneDotZero = rexBuilder.makeExactLiteral(new BigDecimal(1.0)); + + // Check string comparison + checkSimplify(eq(literalAbc, literalAbc), "true"); + checkSimplify(eq(literalAbc, literalDef), "false"); + checkSimplify(ne(literalAbc, literalAbc), "false"); + checkSimplify(ne(literalAbc, literalDef), "true"); + checkSimplify(gt(literalAbc, literalDef), "false"); + checkSimplify(gt(literalDef, literalAbc), "true"); + checkSimplify(gt(literalDef, literalDef), "false"); + checkSimplify(ge(literalAbc, literalDef), "false"); + checkSimplify(ge(literalDef, literalAbc), "true"); + checkSimplify(ge(literalDef, literalDef), "true"); + checkSimplify(lt(literalAbc, literalDef), "true"); + checkSimplify(lt(literalAbc, literalDef), "true"); + checkSimplify(lt(literalDef, literalDef), "false"); + checkSimplify(le(literalAbc, literalDef), "true"); + checkSimplify(le(literalDef, literalAbc), "false"); + checkSimplify(le(literalDef, literalDef), "true"); + + // Check whole number comparison + checkSimplify(eq(literalZero, literalOne), "false"); + checkSimplify(eq(literalOne, literalZero), "false"); + checkSimplify(ne(literalZero, literalOne), "true"); + checkSimplify(ne(literalOne, literalZero), "true"); + checkSimplify(gt(literalZero, literalOne), "false"); + checkSimplify(gt(literalOne, literalZero), "true"); + checkSimplify(gt(literalOne, literalOne), "false"); + checkSimplify(ge(literalZero, literalOne), "false"); + checkSimplify(ge(literalOne, literalZero), "true"); + checkSimplify(ge(literalOne, literalOne), "true"); + checkSimplify(lt(literalZero, literalOne), "true"); + checkSimplify(lt(literalOne, literalZero), "false"); + checkSimplify(lt(literalOne, literalOne), "false"); + checkSimplify(le(literalZero, literalOne), "true"); + checkSimplify(le(literalOne, literalZero), "false"); + checkSimplify(le(literalOne, literalOne), "true"); + + // Check decimal equality comparison + checkSimplify(eq(literalOne, literalOneDotZero), "true"); + checkSimplify(eq(literalOneDotZero, literalOne), "true"); + checkSimplify(ne(literalOne, literalOneDotZero), "false"); + checkSimplify(ne(literalOneDotZero, literalOne), "false"); + + // Check different types shouldn't change simplification + checkSimplifyUnchanged(eq(literalZero, literalAbc)); + checkSimplifyUnchanged(eq(literalAbc, literalZero)); + checkSimplifyUnchanged(ne(literalZero, literalAbc)); + checkSimplifyUnchanged(ne(literalAbc, literalZero)); + checkSimplifyUnchanged(gt(literalZero, literalAbc)); + checkSimplifyUnchanged(gt(literalAbc, literalZero)); + checkSimplifyUnchanged(ge(literalZero, literalAbc)); + checkSimplifyUnchanged(ge(literalAbc, literalZero)); + checkSimplifyUnchanged(lt(literalZero, literalAbc)); + checkSimplifyUnchanged(lt(literalAbc, literalZero)); + checkSimplifyUnchanged(le(literalZero, literalAbc)); + checkSimplifyUnchanged(le(literalAbc, literalZero)); + } + private Calendar cal(int y, int m, int d, int h, int mm, int s) { final Calendar c = Util.calendar(); c.set(Calendar.YEAR, y);
