| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.calcite.rex; |
| |
| import org.apache.calcite.avatica.util.ByteString; |
| import org.apache.calcite.plan.RelOptPredicateList; |
| import org.apache.calcite.plan.RelOptUtil; |
| import org.apache.calcite.plan.Strong; |
| import org.apache.calcite.rel.metadata.NullSentinel; |
| import org.apache.calcite.rel.type.RelDataType; |
| import org.apache.calcite.rel.type.RelDataTypeFactory; |
| import org.apache.calcite.sql.SqlKind; |
| import org.apache.calcite.sql.SqlOperator; |
| import org.apache.calcite.sql.SqlSpecialOperator; |
| import org.apache.calcite.sql.fun.SqlStdOperatorTable; |
| import org.apache.calcite.sql.type.ReturnTypes; |
| import org.apache.calcite.sql.type.SqlOperandTypeChecker; |
| import org.apache.calcite.sql.type.SqlOperandTypeInference; |
| import org.apache.calcite.sql.type.SqlReturnTypeInference; |
| import org.apache.calcite.sql.type.SqlTypeAssignmentRule; |
| import org.apache.calcite.sql.type.SqlTypeName; |
| import org.apache.calcite.util.DateString; |
| import org.apache.calcite.util.ImmutableBitSet; |
| import org.apache.calcite.util.NlsString; |
| import org.apache.calcite.util.RangeSets; |
| import org.apache.calcite.util.Sarg; |
| import org.apache.calcite.util.TestUtil; |
| import org.apache.calcite.util.TimeString; |
| import org.apache.calcite.util.TimestampString; |
| import org.apache.calcite.util.TimestampWithTimeZoneString; |
| import org.apache.calcite.util.Util; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableRangeSet; |
| import com.google.common.collect.LinkedHashMultimap; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.Range; |
| import com.google.common.collect.RangeSet; |
| |
| import org.hamcrest.Matcher; |
| import org.junit.jupiter.api.Disabled; |
| import org.junit.jupiter.api.Test; |
| |
| import java.math.BigDecimal; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.function.Supplier; |
| |
| import static org.apache.calcite.test.Matchers.isRangeSet; |
| |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.hamcrest.CoreMatchers.nullValue; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertFalse; |
| import static org.junit.jupiter.api.Assertions.assertNotEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotSame; |
| import static org.junit.jupiter.api.Assertions.fail; |
| |
| /** |
| * Unit tests for {@link RexProgram} and |
| * {@link org.apache.calcite.rex.RexProgramBuilder}. |
| */ |
| class RexProgramTest extends RexProgramTestBase { |
| /** |
| * Tests construction of a RexProgram. |
| */ |
| @Test void testBuildProgram() { |
| final RexProgramBuilder builder = createProg(0); |
| final RexProgram program = builder.getProgram(false); |
| final String programString = program.toString(); |
| TestUtil.assertEqualsVerbose( |
| "(expr#0..1=[{inputs}], expr#2=[+($0, 1)], expr#3=[77], " |
| + "expr#4=[+($0, $1)], expr#5=[+($0, $0)], expr#6=[+($t4, $t2)], " |
| + "a=[$t6], b=[$t5])", |
| programString); |
| |
| // Normalize the program using the RexProgramBuilder.normalize API. |
| // Note that unused expression '77' is eliminated, input refs (e.g. $0) |
| // become local refs (e.g. $t0), and constants are assigned to locals. |
| final RexProgram normalizedProgram = program.normalize(rexBuilder, null); |
| final String normalizedProgramString = normalizedProgram.toString(); |
| TestUtil.assertEqualsVerbose( |
| "(expr#0..1=[{inputs}], expr#2=[+($t0, $t1)], expr#3=[1], " |
| + "expr#4=[+($t0, $t3)], expr#5=[+($t2, $t4)], " |
| + "expr#6=[+($t0, $t0)], a=[$t5], b=[$t6])", |
| normalizedProgramString); |
| } |
| |
| /** |
| * Tests construction and normalization of a RexProgram. |
| */ |
| @Test void testNormalize() { |
| final RexProgramBuilder builder = createProg(0); |
| final String program = builder.getProgram(true).toString(); |
| TestUtil.assertEqualsVerbose( |
| "(expr#0..1=[{inputs}], expr#2=[+($t0, $t1)], expr#3=[1], " |
| + "expr#4=[+($t0, $t3)], expr#5=[+($t2, $t4)], " |
| + "expr#6=[+($t0, $t0)], a=[$t5], b=[$t6])", |
| program); |
| } |
| |
| /** |
| * Tests construction and normalization of a RexProgram. |
| */ |
| @Test void testElimDups() { |
| final RexProgramBuilder builder = createProg(1); |
| final String unnormalizedProgram = builder.getProgram(false).toString(); |
| TestUtil.assertEqualsVerbose( |
| "(expr#0..1=[{inputs}], expr#2=[+($0, 1)], expr#3=[77], " |
| + "expr#4=[+($0, $1)], expr#5=[+($0, 1)], expr#6=[+($0, $t5)], " |
| + "expr#7=[+($t4, $t2)], a=[$t7], b=[$t6])", |
| unnormalizedProgram); |
| |
| // normalize eliminates duplicates (specifically "+($0, $1)") |
| final RexProgramBuilder builder2 = createProg(1); |
| final String program2 = builder2.getProgram(true).toString(); |
| TestUtil.assertEqualsVerbose( |
| "(expr#0..1=[{inputs}], expr#2=[+($t0, $t1)], expr#3=[1], " |
| + "expr#4=[+($t0, $t3)], expr#5=[+($t2, $t4)], " |
| + "expr#6=[+($t0, $t4)], a=[$t5], b=[$t6])", |
| program2); |
| } |
| |
| /** |
| * Tests how the condition is simplified. |
| */ |
| @Test void testSimplifyCondition() { |
| final RexProgram program = createProg(3).getProgram(false); |
| assertThat(program.toString(), |
| is("(expr#0..1=[{inputs}], expr#2=[+($0, 1)], expr#3=[77], " |
| + "expr#4=[+($0, $1)], expr#5=[+($0, 1)], expr#6=[+($0, $t5)], " |
| + "expr#7=[+($t4, $t2)], expr#8=[5], expr#9=[>($t2, $t8)], " |
| + "expr#10=[true], expr#11=[IS NOT NULL($t5)], expr#12=[false], " |
| + "expr#13=[null:BOOLEAN], expr#14=[CASE($t9, $t10, $t11, $t12, $t13)], " |
| + "expr#15=[NOT($t14)], a=[$t7], b=[$t6], $condition=[$t15])")); |
| |
| assertThat(program.normalize(rexBuilder, simplify).toString(), |
| is("(expr#0..1=[{inputs}], expr#2=[+($t0, $t1)], expr#3=[1], " |
| + "expr#4=[+($t0, $t3)], expr#5=[+($t2, $t4)], " |
| + "expr#6=[+($t0, $t4)], expr#7=[5], expr#8=[<=($t4, $t7)], " |
| + "a=[$t5], b=[$t6], $condition=[$t8])")); |
| } |
| |
| /** |
| * Tests how the condition is simplified. |
| */ |
| @Test void testSimplifyCondition2() { |
| final RexProgram program = createProg(4).getProgram(false); |
| assertThat(program.toString(), |
| is("(expr#0..1=[{inputs}], expr#2=[+($0, 1)], expr#3=[77], " |
| + "expr#4=[+($0, $1)], expr#5=[+($0, 1)], expr#6=[+($0, $t5)], " |
| + "expr#7=[+($t4, $t2)], expr#8=[5], expr#9=[>($t2, $t8)], " |
| + "expr#10=[true], expr#11=[IS NOT NULL($t5)], expr#12=[false], " |
| + "expr#13=[null:BOOLEAN], expr#14=[CASE($t9, $t10, $t11, $t12, $t13)], " |
| + "expr#15=[NOT($t14)], expr#16=[IS TRUE($t15)], a=[$t7], b=[$t6], " |
| + "$condition=[$t16])")); |
| |
| assertThat(program.normalize(rexBuilder, simplify).toString(), |
| is("(expr#0..1=[{inputs}], expr#2=[+($t0, $t1)], expr#3=[1], " |
| + "expr#4=[+($t0, $t3)], expr#5=[+($t2, $t4)], " |
| + "expr#6=[+($t0, $t4)], expr#7=[5], expr#8=[<=($t4, $t7)], " |
| + "a=[$t5], b=[$t6], $condition=[$t8])")); |
| } |
| |
| /** |
| * Checks translation of AND(x, x). |
| */ |
| @Test void testDuplicateAnd() { |
| // RexProgramBuilder used to translate AND(x, x) to x. |
| // Now it translates it to AND(x, x). |
| // The optimization of AND(x, x) => x occurs at a higher level. |
| final RexProgramBuilder builder = createProg(2); |
| final String program = builder.getProgram(true).toString(); |
| TestUtil.assertEqualsVerbose( |
| "(expr#0..1=[{inputs}], expr#2=[+($t0, $t1)], expr#3=[1], " |
| + "expr#4=[+($t0, $t3)], expr#5=[+($t2, $t4)], " |
| + "expr#6=[+($t0, $t0)], expr#7=[>($t2, $t0)], " |
| + "a=[$t5], b=[$t6], $condition=[$t7])", |
| program); |
| } |
| |
| /** |
| * Creates one of several programs. The program generated depends on the |
| * {@code variant} parameter, as follows: |
| * |
| * <ol> |
| * <li><code>select (x + y) + (x + 1) as a, (x + x) as b from t(x, y)</code> |
| * <li><code>select (x + y) + (x + 1) as a, (x + (x + 1)) as b |
| * from t(x, y)</code> |
| * <li><code>select (x + y) + (x + 1) as a, (x + x) as b from t(x, y) |
| * where ((x + y) > 1) and ((x + y) > 1)</code> |
| * <li><code>select (x + y) + (x + 1) as a, (x + x) as b from t(x, y) |
| * where not case |
| * when x + 1 > 5 then true |
| * when y is null then null |
| * else false |
| * end</code> |
| * </ol> |
| */ |
| private RexProgramBuilder createProg(int variant) { |
| assert variant >= 0 && variant <= 4; |
| List<RelDataType> types = |
| Arrays.asList( |
| typeFactory.createSqlType(SqlTypeName.INTEGER), |
| typeFactory.createSqlType(SqlTypeName.INTEGER)); |
| List<String> names = Arrays.asList("x", "y"); |
| RelDataType inputRowType = typeFactory.createStructType(types, names); |
| final RexProgramBuilder builder = |
| new RexProgramBuilder(inputRowType, rexBuilder); |
| // $t0 = x |
| // $t1 = y |
| // $t2 = $t0 + 1 (i.e. x + 1) |
| final RexNode i0 = rexBuilder.makeInputRef( |
| types.get(0), 0); |
| RexLocalRef t2 = |
| builder.addExpr( |
| rexBuilder.makeCall( |
| SqlStdOperatorTable.PLUS, |
| i0, literal(1))); |
| // $t3 = 77 (not used) |
| RexLocalRef t3 = |
| builder.addExpr(literal(77)); |
| Util.discard(t3); |
| // $t4 = $t0 + $t1 (i.e. x + y) |
| final RexNode i1 = rexBuilder.makeInputRef( |
| types.get(1), 1); |
| RexLocalRef t4 = |
| builder.addExpr( |
| rexBuilder.makeCall( |
| SqlStdOperatorTable.PLUS, |
| i0, |
| i1)); |
| RexLocalRef t5; |
| final RexLocalRef t1; |
| switch (variant) { |
| case 0: |
| case 2: |
| // $t5 = $t0 + $t0 (i.e. x + x) |
| t5 = builder.addExpr( |
| rexBuilder.makeCall( |
| SqlStdOperatorTable.PLUS, |
| i0, |
| i0)); |
| t1 = null; |
| break; |
| case 1: |
| case 3: |
| case 4: |
| // $tx = $t0 + 1 |
| t1 = |
| builder.addExpr( |
| rexBuilder.makeCall( |
| SqlStdOperatorTable.PLUS, |
| i0, literal(1))); |
| // $t5 = $t0 + $tx (i.e. x + (x + 1)) |
| t5 = |
| builder.addExpr( |
| rexBuilder.makeCall( |
| SqlStdOperatorTable.PLUS, |
| i0, |
| t1)); |
| break; |
| default: |
| throw new AssertionError("unexpected variant " + variant); |
| } |
| // $t6 = $t4 + $t2 (i.e. (x + y) + (x + 1)) |
| RexLocalRef t6 = |
| builder.addExpr( |
| rexBuilder.makeCall( |
| SqlStdOperatorTable.PLUS, |
| t4, |
| t2)); |
| builder.addProject(t6.getIndex(), "a"); |
| builder.addProject(t5.getIndex(), "b"); |
| |
| final RexLocalRef t7; |
| final RexLocalRef t8; |
| switch (variant) { |
| case 2: |
| // $t7 = $t4 > $i0 (i.e. (x + y) > 0) |
| t7 = |
| builder.addExpr( |
| rexBuilder.makeCall( |
| SqlStdOperatorTable.GREATER_THAN, |
| t4, |
| i0)); |
| // $t8 = $t7 AND $t7 |
| t8 = |
| builder.addExpr( |
| and(t7, t7)); |
| builder.addCondition(t8); |
| builder.addCondition(t7); |
| break; |
| case 3: |
| case 4: |
| // $t7 = 5 |
| t7 = builder.addExpr(literal(5)); |
| // $t8 = $t2 > $t7 (i.e. (x + 1) > 5) |
| t8 = builder.addExpr(gt(t2, t7)); |
| // $t9 = true |
| final RexLocalRef t9 = |
| builder.addExpr(trueLiteral); |
| // $t10 = $t1 is not null (i.e. y is not null) |
| assert t1 != null; |
| final RexLocalRef t10 = |
| builder.addExpr( |
| rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, t1)); |
| // $t11 = false |
| final RexLocalRef t11 = |
| builder.addExpr(falseLiteral); |
| // $t12 = unknown |
| final RexLocalRef t12 = |
| builder.addExpr(nullBool); |
| // $t13 = case when $t8 then $t9 when $t10 then $t11 else $t12 end |
| final RexLocalRef t13 = |
| builder.addExpr(case_(t8, t9, t10, t11, t12)); |
| // $t14 = not $t13 (i.e. not case ... end) |
| final RexLocalRef t14 = |
| builder.addExpr(not(t13)); |
| // don't add 't14 is true' - that is implicit |
| if (variant == 3) { |
| builder.addCondition(t14); |
| } else { |
| // $t15 = $14 is true |
| final RexLocalRef t15 = |
| builder.addExpr( |
| isTrue(t14)); |
| builder.addCondition(t15); |
| } |
| } |
| return builder; |
| } |
| |
| /** Unit test for {@link org.apache.calcite.plan.Strong}. */ |
| @Test void testStrong() { |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| |
| final ImmutableBitSet c = ImmutableBitSet.of(); |
| final ImmutableBitSet c0 = ImmutableBitSet.of(0); |
| final ImmutableBitSet c1 = ImmutableBitSet.of(1); |
| final ImmutableBitSet c01 = ImmutableBitSet.of(0, 1); |
| final ImmutableBitSet c13 = ImmutableBitSet.of(1, 3); |
| |
| // input ref |
| final RexInputRef i0 = rexBuilder.makeInputRef(intType, 0); |
| final RexInputRef i1 = rexBuilder.makeInputRef(intType, 1); |
| |
| assertThat(Strong.isNull(i0, c0), is(true)); |
| assertThat(Strong.isNull(i0, c1), is(false)); |
| assertThat(Strong.isNull(i0, c01), is(true)); |
| assertThat(Strong.isNull(i0, c13), is(false)); |
| |
| // literals are strong iff they are always null |
| assertThat(Strong.isNull(trueLiteral, c), is(false)); |
| assertThat(Strong.isNull(trueLiteral, c13), is(false)); |
| assertThat(Strong.isNull(falseLiteral, c13), is(false)); |
| assertThat(Strong.isNull(nullInt, c), is(true)); |
| assertThat(Strong.isNull(nullInt, c13), is(true)); |
| assertThat(Strong.isNull(nullBool, c13), is(true)); |
| |
| // AND is strong if one of its arguments is strong |
| final RexNode andUnknownTrue = and(nullBool, trueLiteral); |
| final RexNode andTrueUnknown = and(trueLiteral, nullBool); |
| final RexNode andFalseTrue = and(falseLiteral, trueLiteral); |
| |
| assertThat(Strong.isNull(andUnknownTrue, c), is(false)); |
| assertThat(Strong.isNull(andTrueUnknown, c), is(false)); |
| assertThat(Strong.isNull(andFalseTrue, c), is(false)); |
| |
| // If i0 is null, "i0 and i1 is null" is null |
| assertThat(Strong.isNull(and(i0, isNull(i1)), c0), is(false)); |
| // If i1 is null, "i0 and i1" is false |
| assertThat(Strong.isNull(and(i0, isNull(i1)), c1), is(false)); |
| // If i0 and i1 are both null, "i0 and i1" is null |
| assertThat(Strong.isNull(and(i0, i1), c01), is(true)); |
| assertThat(Strong.isNull(and(i0, i1), c1), is(false)); |
| // If i0 and i1 are both null, "i0 and isNull(i1) is false" |
| assertThat(Strong.isNull(and(i0, isNull(i1)), c01), is(false)); |
| // If i0 and i1 are both null, "i0 or i1" is null |
| assertThat(Strong.isNull(or(i0, i1), c01), is(true)); |
| // If i0 is null, "i0 or i1" is not necessarily null |
| assertThat(Strong.isNull(or(i0, i1), c0), is(false)); |
| assertThat(Strong.isNull(or(i0, i1), c1), is(false)); |
| |
| // If i0 is null, then "i0 is not null" is false |
| RexNode i0NotNull = isNotNull(i0); |
| assertThat(Strong.isNull(i0NotNull, c0), is(false)); |
| assertThat(Strong.isNotTrue(i0NotNull, c0), is(true)); |
| |
| // If i0 is null, then "not(i0 is not null)" is true. |
| // Join-strengthening relies on this. |
| RexNode notI0NotNull = not(isNotNull(i0)); |
| assertThat(Strong.isNull(notI0NotNull, c0), is(false)); |
| assertThat(Strong.isNotTrue(notI0NotNull, c0), is(false)); |
| |
| // NULLIF(null, null): null |
| // NULLIF(null, X): null |
| // NULLIF(X, X/Y): null or X |
| // NULLIF(X, null): X |
| assertThat(Strong.isNull(nullIf(nullInt, nullInt), c), is(true)); |
| assertThat(Strong.isNull(nullIf(nullInt, trueLiteral), c), is(true)); |
| assertThat(Strong.isNull(nullIf(trueLiteral, trueLiteral), c), is(false)); |
| assertThat(Strong.isNull(nullIf(trueLiteral, falseLiteral), c), is(false)); |
| assertThat(Strong.isNull(nullIf(trueLiteral, nullInt), c), is(false)); |
| |
| // ISNULL(null) is true, ISNULL(not null value) is false |
| assertThat(Strong.isNull(isNull(nullInt), c01), is(false)); |
| assertThat(Strong.isNull(isNull(trueLiteral), c01), is(false)); |
| |
| // CASE ( <predicate1> <value1> <predicate2> <value2> <predicate3> <value3> ...) |
| // only definitely null if all values are null. |
| assertThat( |
| Strong.isNull( |
| case_(eq(i0, i1), nullInt, ge(i0, i1), nullInt, nullInt), c01), |
| is(true)); |
| assertThat( |
| Strong.isNull( |
| case_(eq(i0, i1), i0, ge(i0, i1), nullInt, nullInt), c01), |
| is(true)); |
| assertThat( |
| Strong.isNull( |
| case_(eq(i0, i1), i0, ge(i0, i1), nullInt, nullInt), c1), |
| is(false)); |
| assertThat( |
| Strong.isNull( |
| case_(eq(i0, i1), nullInt, ge(i0, i1), i0, nullInt), c01), |
| is(true)); |
| assertThat( |
| Strong.isNull( |
| case_(eq(i0, i1), nullInt, ge(i0, i1), i0, nullInt), c1), |
| is(false)); |
| assertThat( |
| Strong.isNull( |
| case_(eq(i0, i1), nullInt, ge(i0, i1), nullInt, i0), c01), |
| is(true)); |
| assertThat( |
| Strong.isNull( |
| case_(eq(i0, i1), nullInt, ge(i0, i1), nullInt, i0), c1), |
| is(false)); |
| assertThat( |
| Strong.isNull( |
| case_(isNotNull(i0), i0, i1), c), |
| is(false)); |
| assertThat( |
| Strong.isNull( |
| case_(isNotNull(i0), i0, i1), c0), |
| is(false)); |
| assertThat( |
| Strong.isNull( |
| case_(isNotNull(i0), i0, i1), c1), |
| is(false)); |
| assertThat( |
| Strong.isNull( |
| case_(isNotNull(i0), i0, i1), c01), |
| is(true)); |
| |
| } |
| |
| @Test void testItemStrong() { |
| final ImmutableBitSet c0 = ImmutableBitSet.of(0); |
| RexNode item = item(input(tArray(tInt()), 0), literal(0)); |
| |
| assertThat(Strong.isStrong(item), is(true)); |
| assertThat(Strong.isNull(item, c0), is(true)); |
| |
| RelDataType mapType = typeFactory.createMapType(tVarchar(), tVarchar()); |
| item = item(input(mapType, 0), literal("abc")); |
| |
| assertThat(Strong.isStrong(item), is(true)); |
| assertThat(Strong.isNull(item, c0), is(true)); |
| } |
| |
| @Test void xAndNotX() { |
| checkSimplify2( |
| and(vBool(), not(vBool()), |
| vBool(1), not(vBool(1))), |
| "AND(null, IS NULL(?0.bool0), IS NULL(?0.bool1))", |
| "false"); |
| |
| checkSimplify2( |
| and(vBool(), |
| vBool(1), not(vBool(1))), |
| "AND(?0.bool0, null, IS NULL(?0.bool1))", |
| "false"); |
| |
| checkSimplify( |
| and(vBool(), not(vBool()), |
| vBoolNotNull(1), not(vBoolNotNull(1))), |
| "false"); |
| } |
| |
| @Disabled("CALCITE-3457: AssertionError in RexSimplify.validateStrongPolicy") |
| @Test void reproducerFor3457() { |
| // Identified with RexProgramFuzzyTest#testFuzzy, seed=4887662474363391810L |
| checkSimplify( |
| eq(unaryMinus(abstractCast(literal(1), tInt(true))), |
| unaryMinus(abstractCast(literal(1), tInt(true)))), |
| "true"); |
| } |
| |
| @Test void testNoCommonReturnTypeFails() { |
| try { |
| final RexNode node = coalesce(vVarchar(1), vInt(2)); |
| fail("expected exception, got " + node); |
| } catch (IllegalArgumentException e) { |
| final String expected = "Cannot infer return type for COALESCE;" |
| + " operand types: [VARCHAR, INTEGER]"; |
| assertThat(e.getMessage(), is(expected)); |
| } |
| } |
| |
| /** Unit test for {@link org.apache.calcite.rex.RexUtil#toCnf}. */ |
| @Test void testCnf() { |
| final RelDataType booleanType = |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN); |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType rowType = typeFactory.builder() |
| .add("a", booleanType) |
| .add("b", booleanType) |
| .add("c", booleanType) |
| .add("d", booleanType) |
| .add("e", booleanType) |
| .add("f", booleanType) |
| .add("g", booleanType) |
| .add("h", intType) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode aRef = rexBuilder.makeFieldAccess(range, 0); |
| final RexNode bRef = rexBuilder.makeFieldAccess(range, 1); |
| final RexNode cRef = rexBuilder.makeFieldAccess(range, 2); |
| final RexNode dRef = rexBuilder.makeFieldAccess(range, 3); |
| final RexNode eRef = rexBuilder.makeFieldAccess(range, 4); |
| final RexNode fRef = rexBuilder.makeFieldAccess(range, 5); |
| final RexNode gRef = rexBuilder.makeFieldAccess(range, 6); |
| final RexNode hRef = rexBuilder.makeFieldAccess(range, 7); |
| |
| final RexNode hEqSeven = eq(hRef, literal(7)); |
| |
| checkCnf(aRef, "?0.a"); |
| checkCnf(trueLiteral, "true"); |
| checkCnf(falseLiteral, "false"); |
| checkCnf(nullBool, "null:BOOLEAN"); |
| checkCnf(and(aRef, bRef), "AND(?0.a, ?0.b)"); |
| checkCnf(and(aRef, bRef, cRef), "AND(?0.a, ?0.b, ?0.c)"); |
| |
| checkCnf(and(or(aRef, bRef), or(cRef, dRef)), |
| "AND(OR(?0.a, ?0.b), OR(?0.c, ?0.d))"); |
| checkCnf(or(and(aRef, bRef), and(cRef, dRef)), |
| "AND(OR(?0.a, ?0.c), OR(?0.a, ?0.d), OR(?0.b, ?0.c), OR(?0.b, ?0.d))"); |
| // Input has nested ORs, output ORs are flat |
| checkCnf(or(and(aRef, bRef), or(cRef, dRef)), |
| "AND(OR(?0.a, ?0.c, ?0.d), OR(?0.b, ?0.c, ?0.d))"); |
| |
| checkCnf(or(aRef, not(and(bRef, not(hEqSeven)))), |
| "OR(?0.a, NOT(?0.b), =(?0.h, 7))"); |
| |
| // apply de Morgan's theorem |
| checkCnf(not(or(aRef, not(bRef))), "AND(NOT(?0.a), ?0.b)"); |
| |
| // apply de Morgan's theorem, |
| // filter out 'OR ... FALSE' and 'AND ... TRUE' |
| checkCnf(not(or(and(aRef, trueLiteral), not(bRef), falseLiteral)), |
| "AND(NOT(?0.a), ?0.b)"); |
| |
| checkCnf(and(aRef, or(bRef, and(cRef, dRef))), |
| "AND(?0.a, OR(?0.b, ?0.c), OR(?0.b, ?0.d))"); |
| |
| checkCnf( |
| and(aRef, or(bRef, and(cRef, or(dRef, and(eRef, or(fRef, gRef)))))), |
| "AND(?0.a, OR(?0.b, ?0.c), OR(?0.b, ?0.d, ?0.e), OR(?0.b, ?0.d, ?0.f, ?0.g))"); |
| |
| checkCnf( |
| and(aRef, |
| or(bRef, |
| and(cRef, |
| or(dRef, |
| and(eRef, |
| or(fRef, |
| and(gRef, or(trueLiteral, falseLiteral)))))))), |
| "AND(?0.a, OR(?0.b, ?0.c), OR(?0.b, ?0.d, ?0.e), OR(?0.b, ?0.d, ?0.f, ?0.g))"); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-394">[CALCITE-394] |
| * Add RexUtil.toCnf, to convert expressions to conjunctive normal form |
| * (CNF)</a>. */ |
| @Test void testCnf2() { |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType rowType = typeFactory.builder() |
| .add("x", intType) |
| .add("y", intType) |
| .add("z", intType) |
| .add("a", intType) |
| .add("b", intType) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode xRef = rexBuilder.makeFieldAccess(range, 0); |
| final RexNode yRef = rexBuilder.makeFieldAccess(range, 1); |
| final RexNode zRef = rexBuilder.makeFieldAccess(range, 2); |
| final RexNode aRef = rexBuilder.makeFieldAccess(range, 3); |
| final RexNode bRef = rexBuilder.makeFieldAccess(range, 4); |
| |
| checkCnf( |
| or( |
| and(eq(xRef, literal(1)), |
| eq(yRef, literal(1)), |
| eq(zRef, literal(1))), |
| and(eq(xRef, literal(2)), |
| eq(yRef, literal(2)), |
| eq(aRef, literal(2))), |
| and(eq(xRef, literal(3)), |
| eq(aRef, literal(3)), |
| eq(bRef, literal(3)))), |
| "AND(" |
| + "OR(=(?0.x, 1), =(?0.x, 2), =(?0.x, 3)), " |
| + "OR(=(?0.x, 1), =(?0.x, 2), =(?0.a, 3)), " |
| + "OR(=(?0.x, 1), =(?0.x, 2), =(?0.b, 3)), " |
| + "OR(=(?0.x, 1), =(?0.y, 2), =(?0.x, 3)), " |
| + "OR(=(?0.x, 1), =(?0.y, 2), =(?0.a, 3)), " |
| + "OR(=(?0.x, 1), =(?0.y, 2), =(?0.b, 3)), " |
| + "OR(=(?0.x, 1), =(?0.a, 2), =(?0.x, 3)), " |
| + "OR(=(?0.x, 1), =(?0.a, 2), =(?0.a, 3)), " |
| + "OR(=(?0.x, 1), =(?0.a, 2), =(?0.b, 3)), " |
| + "OR(=(?0.y, 1), =(?0.x, 2), =(?0.x, 3)), " |
| + "OR(=(?0.y, 1), =(?0.x, 2), =(?0.a, 3)), " |
| + "OR(=(?0.y, 1), =(?0.x, 2), =(?0.b, 3)), " |
| + "OR(=(?0.y, 1), =(?0.y, 2), =(?0.x, 3)), " |
| + "OR(=(?0.y, 1), =(?0.y, 2), =(?0.a, 3)), " |
| + "OR(=(?0.y, 1), =(?0.y, 2), =(?0.b, 3)), " |
| + "OR(=(?0.y, 1), =(?0.a, 2), =(?0.x, 3)), " |
| + "OR(=(?0.y, 1), =(?0.a, 2), =(?0.a, 3)), " |
| + "OR(=(?0.y, 1), =(?0.a, 2), =(?0.b, 3)), " |
| + "OR(=(?0.z, 1), =(?0.x, 2), =(?0.x, 3)), " |
| + "OR(=(?0.z, 1), =(?0.x, 2), =(?0.a, 3)), " |
| + "OR(=(?0.z, 1), =(?0.x, 2), =(?0.b, 3)), " |
| + "OR(=(?0.z, 1), =(?0.y, 2), =(?0.x, 3)), " |
| + "OR(=(?0.z, 1), =(?0.y, 2), =(?0.a, 3)), " |
| + "OR(=(?0.z, 1), =(?0.y, 2), =(?0.b, 3)), " |
| + "OR(=(?0.z, 1), =(?0.a, 2), =(?0.x, 3)), " |
| + "OR(=(?0.z, 1), =(?0.a, 2), =(?0.a, 3)), " |
| + "OR(=(?0.z, 1), =(?0.a, 2), =(?0.b, 3)))"); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-1290">[CALCITE-1290] |
| * When converting to CNF, fail if the expression exceeds a threshold</a>. */ |
| @Test void testThresholdCnf() { |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType rowType = typeFactory.builder() |
| .add("x", intType) |
| .add("y", intType) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode xRef = rexBuilder.makeFieldAccess(range, 0); |
| final RexNode yRef = rexBuilder.makeFieldAccess(range, 1); |
| |
| // Expression |
| // OR(=(?0.x, 1), AND(=(?0.x, 2), =(?0.y, 3))) |
| // transformation creates 7 nodes |
| // AND(OR(=(?0.x, 1), =(?0.x, 2)), OR(=(?0.x, 1), =(?0.y, 3))) |
| // Thus, it is triggered. |
| checkThresholdCnf( |
| or(eq(xRef, literal(1)), |
| and(eq(xRef, literal(2)), eq(yRef, literal(3)))), |
| 8, "AND(OR(=(?0.x, 1), =(?0.x, 2)), OR(=(?0.x, 1), =(?0.y, 3)))"); |
| |
| // Expression |
| // OR(=(?0.x, 1), =(?0.x, 2), AND(=(?0.x, 3), =(?0.y, 4))) |
| // transformation creates 9 nodes |
| // AND(OR(=(?0.x, 1), =(?0.x, 2), =(?0.x, 3)), |
| // OR(=(?0.x, 1), =(?0.x, 2), =(?0.y, 8))) |
| // Thus, it is NOT triggered. |
| checkThresholdCnf( |
| or(eq(xRef, literal(1)), eq(xRef, literal(2)), |
| and(eq(xRef, literal(3)), eq(yRef, literal(4)))), |
| 8, "OR(=(?0.x, 1), =(?0.x, 2), AND(=(?0.x, 3), =(?0.y, 4)))"); |
| } |
| |
| /** Tests formulas of various sizes whose size is exponential when converted |
| * to CNF. */ |
| @Test void testCnfExponential() { |
| // run out of memory if limit is higher than about 20 |
| int limit = 16; |
| for (int i = 2; i < limit; i++) { |
| checkExponentialCnf(i); |
| } |
| } |
| |
| private void checkExponentialCnf(int n) { |
| final RelDataType booleanType = |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN); |
| final RelDataTypeFactory.Builder builder = typeFactory.builder(); |
| for (int i = 0; i < n; i++) { |
| builder.add("x" + i, booleanType) |
| .add("y" + i, booleanType); |
| } |
| final RelDataType rowType3 = builder.build(); |
| final RexDynamicParam range3 = rexBuilder.makeDynamicParam(rowType3, 0); |
| final List<RexNode> list = new ArrayList<>(); |
| for (int i = 0; i < n; i++) { |
| list.add( |
| and(rexBuilder.makeFieldAccess(range3, i * 2), |
| rexBuilder.makeFieldAccess(range3, i * 2 + 1))); |
| } |
| final RexNode cnf = RexUtil.toCnf(rexBuilder, or(list)); |
| final int nodeCount = cnf.nodeCount(); |
| assertThat((n + 1) * (int) Math.pow(2, n) + 1, equalTo(nodeCount)); |
| if (n == 3) { |
| assertThat(cnf.toString(), |
| equalTo("AND(OR(?0.x0, ?0.x1, ?0.x2), OR(?0.x0, ?0.x1, ?0.y2)," |
| + " OR(?0.x0, ?0.y1, ?0.x2), OR(?0.x0, ?0.y1, ?0.y2)," |
| + " OR(?0.y0, ?0.x1, ?0.x2), OR(?0.y0, ?0.x1, ?0.y2)," |
| + " OR(?0.y0, ?0.y1, ?0.x2), OR(?0.y0, ?0.y1, ?0.y2))")); |
| } |
| } |
| |
| /** Unit test for {@link org.apache.calcite.rex.RexUtil#pullFactors}. */ |
| @Test void testPullFactors() { |
| final RelDataType booleanType = |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN); |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType rowType = typeFactory.builder() |
| .add("a", booleanType) |
| .add("b", booleanType) |
| .add("c", booleanType) |
| .add("d", booleanType) |
| .add("e", booleanType) |
| .add("f", booleanType) |
| .add("g", booleanType) |
| .add("h", intType) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode aRef = rexBuilder.makeFieldAccess(range, 0); |
| final RexNode bRef = rexBuilder.makeFieldAccess(range, 1); |
| final RexNode cRef = rexBuilder.makeFieldAccess(range, 2); |
| final RexNode dRef = rexBuilder.makeFieldAccess(range, 3); |
| final RexNode eRef = rexBuilder.makeFieldAccess(range, 4); |
| final RexNode fRef = rexBuilder.makeFieldAccess(range, 5); |
| final RexNode gRef = rexBuilder.makeFieldAccess(range, 6); |
| final RexNode hRef = rexBuilder.makeFieldAccess(range, 7); |
| |
| final RexNode hEqSeven = eq(hRef, literal(7)); |
| |
| // Most of the expressions in testCnf are unaffected by pullFactors. |
| checkPullFactors( |
| or(and(aRef, bRef), |
| and(cRef, aRef, dRef, aRef)), |
| "AND(?0.a, OR(?0.b, AND(?0.c, ?0.d)))"); |
| |
| checkPullFactors(aRef, "?0.a"); |
| checkPullFactors(trueLiteral, "true"); |
| checkPullFactors(falseLiteral, "false"); |
| checkPullFactors(nullBool, "null:BOOLEAN"); |
| checkPullFactors(and(aRef, bRef), "AND(?0.a, ?0.b)"); |
| checkPullFactors(and(aRef, bRef, cRef), "AND(?0.a, ?0.b, ?0.c)"); |
| |
| checkPullFactorsUnchanged(and(or(aRef, bRef), or(cRef, dRef))); |
| checkPullFactorsUnchanged(or(and(aRef, bRef), and(cRef, dRef))); |
| // Input has nested ORs, output ORs are flat; different from CNF |
| checkPullFactors(or(and(aRef, bRef), or(cRef, dRef)), |
| "OR(AND(?0.a, ?0.b), ?0.c, ?0.d)"); |
| |
| checkPullFactorsUnchanged(or(aRef, not(and(bRef, not(hEqSeven))))); |
| checkPullFactorsUnchanged(not(or(aRef, not(bRef)))); |
| checkPullFactorsUnchanged( |
| not(or(and(aRef, trueLiteral), not(bRef), falseLiteral))); |
| checkPullFactorsUnchanged(and(aRef, or(bRef, and(cRef, dRef)))); |
| |
| checkPullFactorsUnchanged( |
| and(aRef, |
| or(bRef, |
| and(cRef, |
| or(dRef, and(eRef, or(fRef, gRef))))))); |
| |
| checkPullFactorsUnchanged( |
| and(aRef, |
| or(bRef, |
| and(cRef, |
| or(dRef, |
| and(eRef, |
| or(fRef, |
| and(gRef, or(trueLiteral, falseLiteral))))))))); |
| } |
| |
| @Test void testSimplify() { |
| final RelDataType booleanType = |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN); |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType intNullableType = |
| typeFactory.createTypeWithNullability(intType, true); |
| final RelDataType rowType = typeFactory.builder() |
| .add("a", booleanType) |
| .add("b", booleanType) |
| .add("c", booleanType) |
| .add("d", booleanType) |
| .add("e", booleanType) |
| .add("f", booleanType) |
| .add("g", booleanType) |
| .add("h", intType) |
| .add("i", intNullableType) |
| .add("j", intType) |
| .add("k", intType) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode aRef = rexBuilder.makeFieldAccess(range, 0); |
| final RexNode bRef = rexBuilder.makeFieldAccess(range, 1); |
| final RexNode cRef = rexBuilder.makeFieldAccess(range, 2); |
| final RexNode dRef = rexBuilder.makeFieldAccess(range, 3); |
| final RexNode eRef = rexBuilder.makeFieldAccess(range, 4); |
| final RexNode hRef = rexBuilder.makeFieldAccess(range, 7); |
| final RexNode iRef = rexBuilder.makeFieldAccess(range, 8); |
| final RexNode jRef = rexBuilder.makeFieldAccess(range, 9); |
| final RexNode kRef = rexBuilder.makeFieldAccess(range, 10); |
| |
| // and: remove duplicates |
| checkSimplify(and(aRef, bRef, aRef), "AND(?0.a, ?0.b)"); |
| |
| // and: remove true |
| checkSimplify(and(aRef, bRef, trueLiteral), |
| "AND(?0.a, ?0.b)"); |
| |
| // and: false falsifies |
| checkSimplify(and(aRef, bRef, falseLiteral), |
| "false"); |
| |
| // and: remove duplicate "not"s |
| checkSimplify(and(not(aRef), bRef, not(cRef), not(aRef)), |
| "AND(?0.b, NOT(?0.a), NOT(?0.c))"); |
| |
| // and: "not true" falsifies |
| checkSimplify(and(not(aRef), bRef, not(trueLiteral)), |
| "false"); |
| |
| // and: flatten and remove duplicates |
| checkSimplify( |
| and(aRef, and(and(bRef, not(cRef), dRef, not(eRef)), not(eRef))), |
| "AND(?0.a, ?0.b, ?0.d, NOT(?0.c), NOT(?0.e))"); |
| |
| // and: expand "... and not(or(x, y))" to "... and not(x) and not(y)" |
| checkSimplify(and(aRef, bRef, not(or(cRef, or(dRef, eRef)))), |
| "AND(?0.a, ?0.b, NOT(?0.c), NOT(?0.d), NOT(?0.e))"); |
| |
| checkSimplify(and(aRef, bRef, not(or(not(cRef), dRef, not(eRef)))), |
| "AND(?0.a, ?0.b, ?0.c, ?0.e, NOT(?0.d))"); |
| |
| // or: remove duplicates |
| checkSimplify(or(aRef, bRef, aRef), "OR(?0.a, ?0.b)"); |
| |
| // or: remove false |
| checkSimplify(or(aRef, bRef, falseLiteral), |
| "OR(?0.a, ?0.b)"); |
| |
| // or: true makes everything true |
| checkSimplify(or(aRef, bRef, trueLiteral), "true"); |
| |
| // case: remove false branches |
| checkSimplify(case_(eq(bRef, cRef), dRef, falseLiteral, aRef, eRef), |
| "OR(AND(=(?0.b, ?0.c), ?0.d), AND(?0.e, <>(?0.b, ?0.c)))"); |
| |
| // case: true branches become the last branch |
| checkSimplify( |
| case_(eq(bRef, cRef), dRef, trueLiteral, aRef, eq(cRef, dRef), eRef, cRef), |
| "OR(AND(=(?0.b, ?0.c), ?0.d), AND(?0.a, <>(?0.b, ?0.c)))"); |
| |
| // case: singleton |
| checkSimplify(case_(trueLiteral, aRef, eq(cRef, dRef), eRef, cRef), "?0.a"); |
| |
| // case: always same value |
| checkSimplify( |
| case_(aRef, literal(1), bRef, literal(1), cRef, literal(1), dRef, |
| literal(1), literal(1)), "1"); |
| |
| // case: trailing false and null, no simplification |
| checkSimplify3( |
| case_(aRef, trueLiteral, bRef, trueLiteral, cRef, falseLiteral, nullBool), |
| "OR(?0.a, ?0.b, AND(null, NOT(?0.a), NOT(?0.b), NOT(?0.c)))", |
| "OR(?0.a, ?0.b)", |
| "OR(?0.a, ?0.b, NOT(?0.c))"); |
| |
| // case: form an AND of branches that return true |
| checkSimplify( |
| case_(aRef, trueLiteral, bRef, |
| falseLiteral, cRef, |
| falseLiteral, dRef, trueLiteral, |
| falseLiteral), |
| "OR(?0.a, AND(?0.d, NOT(?0.b), NOT(?0.c)))"); |
| |
| checkSimplify( |
| case_(aRef, trueLiteral, bRef, |
| falseLiteral, cRef, |
| falseLiteral, dRef, trueLiteral, eRef, |
| falseLiteral, trueLiteral), |
| "OR(?0.a, AND(?0.d, NOT(?0.b), NOT(?0.c)), AND(NOT(?0.b), NOT(?0.c), NOT(?0.e)))"); |
| |
| checkSimplify( |
| case_(eq(falseLiteral, falseLiteral), falseLiteral, |
| eq(falseLiteral, falseLiteral), trueLiteral, |
| trueLiteral), |
| "false"); |
| |
| // is null, applied to not-null value |
| checkSimplify(rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, aRef), |
| "false"); |
| |
| // is not null, applied to not-null value |
| checkSimplify(rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, aRef), |
| "true"); |
| |
| // condition, and the inverse |
| checkSimplify3(and(le(hRef, literal(1)), gt(hRef, literal(1))), |
| "<>(?0.h, ?0.h)", |
| "false", |
| "false"); |
| |
| checkSimplify(and(le(hRef, literal(1)), ge(hRef, literal(1))), "=(?0.h, 1)"); |
| |
| checkSimplify3(and(lt(hRef, literal(1)), eq(hRef, literal(1)), ge(hRef, literal(1))), |
| "<>(?0.h, ?0.h)", |
| "false", |
| "false"); |
| |
| checkSimplify(and(lt(hRef, literal(1)), or(falseLiteral, falseLiteral)), |
| "false"); |
| checkSimplify(and(lt(hRef, literal(1)), or(falseLiteral, gt(jRef, kRef))), |
| "AND(<(?0.h, 1), >(?0.j, ?0.k))"); |
| checkSimplify(or(lt(hRef, literal(1)), and(trueLiteral, trueLiteral)), |
| "true"); |
| checkSimplify( |
| or(lt(hRef, literal(1)), |
| and(trueLiteral, or(trueLiteral, falseLiteral))), |
| "true"); |
| checkSimplify( |
| or(lt(hRef, literal(1)), |
| and(trueLiteral, and(trueLiteral, falseLiteral))), |
| "<(?0.h, 1)"); |
| checkSimplify( |
| or(lt(hRef, literal(1)), |
| and(trueLiteral, or(falseLiteral, falseLiteral))), |
| "<(?0.h, 1)"); |
| |
| // "x = x" simplifies to "x is not null" |
| checkSimplify(eq(literal(1), literal(1)), "true"); |
| checkSimplify(eq(hRef, hRef), "true"); |
| checkSimplify3(eq(iRef, iRef), "OR(null, IS NOT NULL(?0.i))", "IS NOT NULL(?0.i)", "true"); |
| checkSimplifyUnchanged(eq(iRef, hRef)); |
| |
| // "x <= x" simplifies to "x is not null" |
| checkSimplify(le(literal(1), literal(1)), "true"); |
| checkSimplify(le(hRef, hRef), "true"); |
| checkSimplify3(le(iRef, iRef), "OR(null, IS NOT NULL(?0.i))", "IS NOT NULL(?0.i)", "true"); |
| checkSimplifyUnchanged(le(iRef, hRef)); |
| |
| // "x >= x" simplifies to "x is not null" |
| checkSimplify(ge(literal(1), literal(1)), "true"); |
| checkSimplify(ge(hRef, hRef), "true"); |
| checkSimplify3(ge(iRef, iRef), "OR(null, IS NOT NULL(?0.i))", "IS NOT NULL(?0.i)", "true"); |
| checkSimplifyUnchanged(ge(iRef, hRef)); |
| |
| // "x <> x" simplifies to "false" |
| checkSimplify(ne(literal(1), literal(1)), "false"); |
| checkSimplify(ne(hRef, hRef), "false"); |
| checkSimplify3(ne(iRef, iRef), "AND(null, IS NULL(?0.i))", |
| "false", "IS NULL(?0.i)"); |
| checkSimplifyUnchanged(ne(iRef, hRef)); |
| |
| // "x < x" simplifies to "false" |
| checkSimplify(lt(literal(1), literal(1)), "false"); |
| checkSimplify(lt(hRef, hRef), "false"); |
| checkSimplify3(lt(iRef, iRef), "AND(null, IS NULL(?0.i))", |
| "false", "IS NULL(?0.i)"); |
| checkSimplifyUnchanged(lt(iRef, hRef)); |
| |
| // "x > x" simplifies to "false" |
| checkSimplify(gt(literal(1), literal(1)), "false"); |
| checkSimplify(gt(hRef, hRef), "false"); |
| checkSimplify3(gt(iRef, iRef), "AND(null, IS NULL(?0.i))", |
| "false", "IS NULL(?0.i)"); |
| checkSimplifyUnchanged(gt(iRef, hRef)); |
| |
| // "(not x) is null" to "x is null" |
| checkSimplify(isNull(not(vBool())), "IS NULL(?0.bool0)"); |
| checkSimplify(isNull(not(vBoolNotNull())), "false"); |
| |
| // "(not x) is not null" to "x is not null" |
| checkSimplify(isNotNull(not(vBool())), "IS NOT NULL(?0.bool0)"); |
| checkSimplify(isNotNull(not(vBoolNotNull())), "true"); |
| |
| // "null is null" to "true" |
| checkSimplify(isNull(nullBool), "true"); |
| // "(x + y) is null" simplifies to "x is null or y is null" |
| checkSimplify(isNull(plus(vInt(0), vInt(1))), |
| "OR(IS NULL(?0.int0), IS NULL(?0.int1))"); |
| checkSimplify(isNull(plus(vInt(0), vIntNotNull(1))), "IS NULL(?0.int0)"); |
| checkSimplify(isNull(plus(vIntNotNull(0), vIntNotNull(1))), "false"); |
| checkSimplify(isNull(plus(vIntNotNull(0), vInt(1))), "IS NULL(?0.int1)"); |
| |
| // "(x + y) is not null" simplifies to "x is not null and y is not null" |
| checkSimplify(isNotNull(plus(vInt(0), vInt(1))), |
| "AND(IS NOT NULL(?0.int0), IS NOT NULL(?0.int1))"); |
| checkSimplify(isNotNull(plus(vInt(0), vIntNotNull(1))), |
| "IS NOT NULL(?0.int0)"); |
| checkSimplify(isNotNull(plus(vIntNotNull(0), vIntNotNull(1))), "true"); |
| checkSimplify(isNotNull(plus(vIntNotNull(0), vInt(1))), |
| "IS NOT NULL(?0.int1)"); |
| } |
| |
| @Test void simplifyStrong() { |
| checkSimplify(ge(trueLiteral, falseLiteral), "true"); |
| checkSimplify3(ge(trueLiteral, nullBool), "null:BOOLEAN", "false", "true"); |
| checkSimplify3(ge(nullBool, nullBool), "null:BOOLEAN", "false", "true"); |
| checkSimplify3(gt(trueLiteral, nullBool), "null:BOOLEAN", "false", "true"); |
| checkSimplify3(le(trueLiteral, nullBool), "null:BOOLEAN", "false", "true"); |
| checkSimplify3(lt(trueLiteral, nullBool), "null:BOOLEAN", "false", "true"); |
| |
| checkSimplify3(not(nullBool), "null:BOOLEAN", "false", "true"); |
| checkSimplify3(ne(vInt(), nullBool), "null:BOOLEAN", "false", "true"); |
| checkSimplify3(eq(vInt(), nullBool), "null:BOOLEAN", "false", "true"); |
| |
| checkSimplify(plus(vInt(), nullInt), "null:INTEGER"); |
| checkSimplify(sub(vInt(), nullInt), "null:INTEGER"); |
| checkSimplify(mul(vInt(), nullInt), "null:INTEGER"); |
| checkSimplify(div(vInt(), nullInt), "null:INTEGER"); |
| } |
| |
| @Test void testSimplifyFilter() { |
| final RelDataType booleanType = |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN); |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType rowType = typeFactory.builder() |
| .add("a", intType) |
| .add("b", intType) |
| .add("c", booleanType) |
| .add("d", booleanType) |
| .add("e", booleanType) |
| .add("f", booleanType) |
| .add("g", booleanType) |
| .add("h", intType) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode aRef = rexBuilder.makeFieldAccess(range, 0); |
| final RexNode bRef = rexBuilder.makeFieldAccess(range, 1); |
| final RexNode cRef = rexBuilder.makeFieldAccess(range, 2); |
| final RexNode dRef = rexBuilder.makeFieldAccess(range, 3); |
| final RexNode eRef = rexBuilder.makeFieldAccess(range, 4); |
| final RexNode fRef = rexBuilder.makeFieldAccess(range, 5); |
| |
| // condition, and the inverse |
| checkSimplifyFilter(and(le(aRef, literal(1)), gt(aRef, literal(1))), |
| "false"); |
| |
| checkSimplifyFilter(and(le(aRef, literal(1)), ge(aRef, literal(1))), |
| "=(?0.a, 1)"); |
| |
| checkSimplifyFilter( |
| and(lt(aRef, literal(1)), eq(aRef, literal(1)), ge(aRef, literal(1))), |
| "false"); |
| |
| // simplify equals boolean |
| final ImmutableList<RexNode> args = |
| ImmutableList.of(eq(eq(aRef, literal(1)), trueLiteral), |
| eq(bRef, literal(1))); |
| checkSimplifyFilter(and(args), |
| "AND(=(?0.a, 1), =(?0.b, 1))"); |
| |
| // as previous, using simplifyFilterPredicates |
| assertThat(simplify |
| .simplifyFilterPredicates(args) |
| .toString(), |
| equalTo("AND(=(?0.a, 1), =(?0.b, 1))")); |
| |
| // "a = 1 and a = 10" is always false |
| final ImmutableList<RexNode> args2 = |
| ImmutableList.of(eq(aRef, literal(1)), eq(aRef, literal(10))); |
| checkSimplifyFilter(and(args2), "false"); |
| |
| assertThat(simplify |
| .simplifyFilterPredicates(args2), |
| nullValue()); |
| |
| // equality on constants, can remove the equality on the variables |
| checkSimplifyFilter(and(eq(aRef, literal(1)), eq(bRef, literal(1)), eq(aRef, bRef)), |
| "AND(=(?0.a, 1), =(?0.b, 1))"); |
| |
| // condition not satisfiable |
| checkSimplifyFilter(and(eq(aRef, literal(1)), eq(bRef, literal(10)), eq(aRef, bRef)), |
| "false"); |
| |
| // condition not satisfiable |
| checkSimplifyFilter(and(gt(aRef, literal(10)), ge(bRef, literal(1)), lt(aRef, literal(10))), |
| "false"); |
| |
| // one "and" containing three "or"s |
| checkSimplifyFilter( |
| or(gt(aRef, literal(10)), gt(bRef, literal(1)), gt(aRef, literal(10))), |
| "OR(>(?0.a, 10), >(?0.b, 1))"); |
| |
| // case: trailing false and null, remove |
| checkSimplifyFilter( |
| case_(cRef, trueLiteral, dRef, trueLiteral, eRef, falseLiteral, fRef, |
| falseLiteral, nullBool), |
| "OR(?0.c, ?0.d)"); |
| |
| // condition with null value for range |
| checkSimplifyFilter(and(gt(aRef, nullBool), ge(bRef, literal(1))), "false"); |
| |
| // condition "1 < a && 5 < x" yields "5 < x" |
| checkSimplifyFilter( |
| and(lt(literal(1), aRef), lt(literal(5), aRef)), |
| RelOptPredicateList.EMPTY, |
| ">(?0.a, 5)"); |
| |
| // condition "1 < a && a < 5" is converted to a Sarg |
| checkSimplifyFilter( |
| and(lt(literal(1), aRef), lt(aRef, literal(5))), |
| RelOptPredicateList.EMPTY, |
| "SEARCH(?0.a, Sarg[(1..5)])"); |
| |
| // condition "1 > a && 5 > x" yields "1 > a" |
| checkSimplifyFilter( |
| and(gt(literal(1), aRef), gt(literal(5), aRef)), |
| RelOptPredicateList.EMPTY, |
| "<(?0.a, 1)"); |
| |
| // condition "1 > a && a > 5" yields false |
| checkSimplifyFilter( |
| and(gt(literal(1), aRef), gt(aRef, literal(5))), |
| RelOptPredicateList.EMPTY, |
| "false"); |
| |
| // range with no predicates; |
| // condition "a > 1 && a < 10 && a < 5" yields "a < 1 && a < 5" |
| checkSimplifyFilter( |
| and(gt(aRef, literal(1)), lt(aRef, literal(10)), lt(aRef, literal(5))), |
| RelOptPredicateList.EMPTY, |
| "SEARCH(?0.a, Sarg[(1..5)])"); |
| |
| // condition "a > 1 && a < 10 && a < 5" |
| // with pre-condition "a > 5" |
| // yields "false" |
| checkSimplifyFilter( |
| and(gt(aRef, literal(1)), lt(aRef, literal(10)), lt(aRef, literal(5))), |
| RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(gt(aRef, literal(5)))), |
| "false"); |
| |
| // condition "a > 1 && a < 10 && a <= 5" |
| // with pre-condition "a >= 5" |
| // yields "a = 5" |
| // "a <= 5" would also be correct, just a little less concise. |
| checkSimplifyFilter( |
| and(gt(aRef, literal(1)), lt(aRef, literal(10)), le(aRef, literal(5))), |
| RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(ge(aRef, literal(5)))), |
| "=(?0.a, 5)"); |
| |
| // condition "a > 1 && a < 10 && a < 5" |
| // with pre-condition "b < 10 && a > 5" |
| // yields "a > 1 and a < 5" |
| checkSimplifyFilter( |
| and(gt(aRef, literal(1)), lt(aRef, literal(10)), lt(aRef, literal(5))), |
| RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(lt(bRef, literal(10)), ge(aRef, literal(1)))), |
| "SEARCH(?0.a, Sarg[(1..5)])"); |
| |
| // condition "a > 1" |
| // with pre-condition "b < 10 && a > 5" |
| // yields "true" |
| checkSimplifyFilter(gt(aRef, literal(1)), |
| RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(lt(bRef, literal(10)), gt(aRef, literal(5)))), |
| "true"); |
| |
| // condition "a < 1" |
| // with pre-condition "b < 10 && a > 5" |
| // yields "false" |
| checkSimplifyFilter(lt(aRef, literal(1)), |
| RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(lt(bRef, literal(10)), gt(aRef, literal(5)))), |
| "false"); |
| |
| // condition "a > 5" |
| // with pre-condition "b < 10 && a >= 5" |
| // yields "a > 5" |
| checkSimplifyFilter(gt(aRef, literal(5)), |
| RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(lt(bRef, literal(10)), ge(aRef, literal(5)))), |
| ">(?0.a, 5)"); |
| |
| // condition "a > 5" |
| // with pre-condition "a <= 5" |
| // yields "false" |
| checkSimplifyFilter(gt(aRef, literal(5)), |
| RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(le(aRef, literal(5)))), |
| "false"); |
| |
| // condition "a > 5" |
| // with pre-condition "a <= 5 and b <= 5" |
| // yields "false" |
| checkSimplifyFilter(gt(aRef, literal(5)), |
| RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(le(aRef, literal(5)), le(bRef, literal(5)))), |
| "false"); |
| |
| // condition "a > 5 or b > 5" |
| // with pre-condition "a <= 5 and b <= 5" |
| // should yield "false" but yields "a = 5 or b = 5" |
| checkSimplifyFilter(or(gt(aRef, literal(5)), gt(bRef, literal(5))), |
| RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(le(aRef, literal(5)), le(bRef, literal(5)))), |
| "false"); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198] |
| * Enhance RexSimplify to handle (x<>a or x<>b)</a>. */ |
| @Test void testSimplifyOrNotEqualsNotNullable() { |
| checkSimplify( |
| or( |
| ne(vIntNotNull(), literal(1)), |
| ne(vIntNotNull(), literal(2))), |
| "true"); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198] |
| * Enhance RexSimplify to handle (x<>a or x<>b)</a>. */ |
| @Test void testSimplifyOrNotEqualsNotNullable2() { |
| checkSimplify( |
| or( |
| ne(vIntNotNull(0), literal(1)), |
| eq(vIntNotNull(1), literal(10)), |
| ne(vIntNotNull(0), literal(2))), |
| "true"); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198] |
| * Enhance RexSimplify to handle (x<>a or x<>b)</a>. */ |
| @Test void testSimplifyOrNotEqualsNullable() { |
| checkSimplify3( |
| or( |
| ne(vInt(), literal(1)), |
| ne(vInt(), literal(2))), |
| "OR(IS NOT NULL(?0.int0), null)", |
| "IS NOT NULL(?0.int0)", |
| "true"); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198] |
| * Enhance RexSimplify to handle (x<>a or x<>b)</a>. */ |
| @Test void testSimplifyOrNotEqualsNullable2() { |
| checkSimplify3( |
| or( |
| ne(vInt(0), literal(1)), |
| eq(vInt(1), literal(10)), |
| ne(vInt(0), literal(2))), |
| "OR(IS NOT NULL(?0.int0), null, =(?0.int1, 10))", |
| "OR(IS NOT NULL(?0.int0), =(?0.int1, 10))", |
| "true"); |
| } |
| |
| @Test void testSimplifyOrNotEqualsNull() { |
| checkSimplify3( |
| or( |
| ne(vInt(0), literal(1)), |
| eq(vInt(1), nullInt), |
| ne(vInt(0), literal(2))), |
| "OR(IS NOT NULL(?0.int0), null)", |
| "IS NOT NULL(?0.int0)", |
| "true"); |
| } |
| |
| @Test void testSimplifyAndPush() { |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType rowType = typeFactory.builder() |
| .add("a", intType) |
| .add("b", intType) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode aRef = rexBuilder.makeFieldAccess(range, 0); |
| final RexNode bRef = rexBuilder.makeFieldAccess(range, 1); |
| |
| checkSimplifyFilter( |
| or( |
| or(eq(aRef, literal(1)), |
| eq(aRef, literal(1))), |
| eq(aRef, literal(1))), |
| "=(?0.a, 1)"); |
| |
| checkSimplifyFilter( |
| or( |
| and(eq(aRef, literal(1)), |
| eq(aRef, literal(1))), |
| and(eq(aRef, literal(10)), |
| eq(aRef, literal(1)))), |
| "=(?0.a, 1)"); |
| |
| checkSimplifyFilter( |
| and( |
| eq(aRef, literal(1)), |
| or(eq(aRef, literal(1)), |
| eq(aRef, literal(10)))), |
| "=(?0.a, 1)"); |
| checkSimplifyFilter( |
| and( |
| or(eq(aRef, literal(1)), |
| eq(aRef, literal(10))), |
| eq(aRef, literal(1))), |
| "=(?0.a, 1)"); |
| |
| checkSimplifyFilter( |
| and(gt(aRef, literal(10)), |
| gt(aRef, literal(1))), |
| ">(?0.a, 10)"); |
| |
| checkSimplifyFilter( |
| and(gt(aRef, literal(1)), |
| gt(aRef, literal(10))), |
| ">(?0.a, 10)"); |
| |
| // "null AND NOT(null OR x)" => "null AND NOT(x)" |
| checkSimplify3( |
| and(nullBool, |
| not(or(nullBool, vBool()))), |
| "AND(null, NOT(?0.bool0))", |
| "false", |
| "NOT(?0.bool0)"); |
| |
| // "x1 AND x2 AND x3 AND NOT(x1) AND NOT(x2) AND NOT(x0)" => |
| // "x3 AND null AND x1 IS NULL AND x2 IS NULL AND NOT(x0)" |
| checkSimplify2( |
| and(vBool(1), vBool(2), |
| vBool(3), not(vBool(1)), |
| not(vBool(2)), not(vBool())), |
| "AND(?0.bool3, null, IS NULL(?0.bool1)," |
| + " IS NULL(?0.bool2), NOT(?0.bool0))", |
| "false"); |
| } |
| |
| @SuppressWarnings("UnstableApiUsage") |
| @Test void testRangeSetMinus() { |
| final RangeSet<Integer> setNone = ImmutableRangeSet.of(); |
| final RangeSet<Integer> setAll = setNone.complement(); |
| final RangeSet<Integer> setGt2 = ImmutableRangeSet.of(Range.greaterThan(2)); |
| final RangeSet<Integer> setGt1 = ImmutableRangeSet.of(Range.greaterThan(1)); |
| final RangeSet<Integer> setGe1 = ImmutableRangeSet.of(Range.atLeast(1)); |
| final RangeSet<Integer> setGt0 = ImmutableRangeSet.of(Range.greaterThan(0)); |
| final RangeSet<Integer> setComplex = |
| ImmutableRangeSet.<Integer>builder() |
| .add(Range.closed(0, 2)) |
| .add(Range.singleton(3)) |
| .add(Range.greaterThan(5)) |
| .build(); |
| assertThat(setComplex, isRangeSet("[[0..2], [3..3], (5..+\u221e)]")); |
| |
| assertThat(RangeSets.minus(setAll, Range.singleton(1)), |
| isRangeSet("[(-\u221e..1), (1..+\u221e)]")); |
| assertThat(RangeSets.minus(setNone, Range.singleton(1)), is(setNone)); |
| assertThat(RangeSets.minus(setGt2, Range.singleton(1)), is(setGt2)); |
| assertThat(RangeSets.minus(setGt1, Range.singleton(1)), is(setGt1)); |
| assertThat(RangeSets.minus(setGe1, Range.singleton(1)), is(setGt1)); |
| assertThat(RangeSets.minus(setGt0, Range.singleton(1)), |
| isRangeSet("[(0..1), (1..+\u221e)]")); |
| assertThat(RangeSets.minus(setComplex, Range.singleton(1)), |
| isRangeSet("[[0..1), (1..2], [3..3], (5..+\u221e)]")); |
| assertThat(RangeSets.minus(setComplex, Range.singleton(2)), |
| isRangeSet("[[0..2), [3..3], (5..+\u221e)]")); |
| assertThat(RangeSets.minus(setComplex, Range.singleton(3)), |
| isRangeSet("[[0..2], (5..+\u221e)]")); |
| assertThat(RangeSets.minus(setComplex, Range.open(2, 3)), |
| isRangeSet("[[0..2], [3..3], (5..+\u221e)]")); |
| assertThat(RangeSets.minus(setComplex, Range.closed(2, 3)), |
| isRangeSet("[[0..2), (5..+\u221e)]")); |
| assertThat(RangeSets.minus(setComplex, Range.closed(2, 7)), |
| isRangeSet("[[0..2), (7..+\u221e)]")); |
| } |
| |
| @Test void testSimplifyOrTerms() { |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType boolType = typeFactory.createSqlType(SqlTypeName.BOOLEAN); |
| final RelDataType rowType = typeFactory.builder() |
| .add("a", intType).nullable(false) |
| .add("b", intType).nullable(true) |
| .add("c", intType).nullable(true) |
| .add("d", boolType).nullable(true) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode aRef = rexBuilder.makeFieldAccess(range, 0); |
| final RexNode bRef = rexBuilder.makeFieldAccess(range, 1); |
| final RexNode cRef = rexBuilder.makeFieldAccess(range, 2); |
| final RexNode dRef = rexBuilder.makeFieldAccess(range, 3); |
| final RexLiteral literal1 = rexBuilder.makeExactLiteral(BigDecimal.ONE); |
| final RexLiteral literal2 = rexBuilder.makeExactLiteral(BigDecimal.valueOf(2)); |
| final RexLiteral literal3 = rexBuilder.makeExactLiteral(BigDecimal.valueOf(3)); |
| final RexLiteral literal4 = rexBuilder.makeExactLiteral(BigDecimal.valueOf(4)); |
| final RexLiteral literal5 = rexBuilder.makeExactLiteral(BigDecimal.valueOf(5)); |
| |
| // "a <> 1 or a = 1" ==> "true" |
| checkSimplifyFilter( |
| or(ne(aRef, literal1), |
| eq(aRef, literal1)), |
| "true"); |
| |
| // "a = 1 or a <> 1" ==> "true" |
| checkSimplifyFilter( |
| or(eq(aRef, literal1), |
| ne(aRef, literal1)), |
| "true"); |
| |
| // "a = 1 or a <> 2" could (and should) be simplified to "a <> 2" |
| // but can't do that right now |
| checkSimplifyFilter( |
| or(eq(aRef, literal1), |
| ne(aRef, literal2)), |
| "OR(=(?0.a, 1), <>(?0.a, 2))"); |
| |
| // "(a >= 1 and a <= 3) or a <> 2", or equivalently |
| // "a between 1 and 3 or a <> 2" ==> "true" |
| checkSimplifyFilter( |
| or( |
| and(ge(aRef, literal1), |
| le(aRef, literal3)), |
| ne(aRef, literal2)), |
| "true"); |
| |
| // "(a >= 1 and a <= 3) or a < 4" ==> "a < 4" |
| checkSimplifyFilter( |
| or( |
| and(ge(aRef, literal1), |
| le(aRef, literal3)), |
| lt(aRef, literal4)), |
| "<(?0.a, 4)"); |
| |
| // "(a >= 1 and a <= 2) or (a >= 4 and a <= 5) or a <> 3" ==> "a <> 3" |
| checkSimplifyFilter( |
| or( |
| and(ge(aRef, literal1), |
| le(aRef, literal2)), |
| and(ge(aRef, literal4), |
| le(aRef, literal5)), |
| ne(aRef, literal3)), |
| "<>(?0.a, 3)"); |
| |
| // "(a >= 1 and a <= 2) or (a >= 4 and a <= 5) or a <> 4" ==> "true" |
| checkSimplifyFilter( |
| or( |
| and(ge(aRef, literal1), |
| le(aRef, literal2)), |
| and(ge(aRef, literal4), |
| le(aRef, literal5)), |
| ne(aRef, literal4)), |
| "true"); |
| |
| // "(a >= 1 and a <= 2) or (a > 4 and a <= 5) or a <> 4" ==> "a <> 4" |
| checkSimplifyFilter( |
| or( |
| and(ge(aRef, literal1), |
| le(aRef, literal2)), |
| and(gt(aRef, literal4), |
| le(aRef, literal5)), |
| ne(aRef, literal4)), |
| "<>(?0.a, 4)"); |
| |
| // "b <> 1 or b = 1" ==> "b is not null" with unknown as false |
| final RexNode neOrEq = |
| or(ne(bRef, literal(1)), |
| eq(bRef, literal(1))); |
| checkSimplifyFilter(neOrEq, "IS NOT NULL(?0.b)"); |
| |
| // Careful of the excluded middle! |
| // We cannot simplify "b <> 1 or b = 1" to "true" because if b is null, the |
| // result is unknown. |
| // TODO: "b = b" would be the best simplification. |
| final RexNode simplified = |
| this.simplify.simplifyUnknownAs(neOrEq, RexUnknownAs.UNKNOWN); |
| assertThat(simplified.toString(), |
| equalTo("OR(IS NOT NULL(?0.b), null)")); |
| |
| // "a is null or a is not null" ==> "true" |
| checkSimplifyFilter( |
| or(isNull(aRef), |
| isNotNull(aRef)), |
| "true"); |
| |
| // "a is not null or a is null" ==> "true" |
| checkSimplifyFilter( |
| or(isNotNull(aRef), |
| isNull(aRef)), |
| "true"); |
| |
| // "b is not null or b is null" ==> "true" (valid even though b nullable) |
| checkSimplifyFilter( |
| or(isNotNull(bRef), |
| isNull(bRef)), |
| "true"); |
| |
| // "b is null b > 1 or b <= 1" ==> "true" |
| checkSimplifyFilter( |
| or(isNull(bRef), |
| gt(bRef, literal(1)), |
| le(bRef, literal(1))), |
| "true"); |
| |
| // "b > 1 or b <= 1 or b is null" ==> "true" |
| checkSimplifyFilter( |
| or(gt(bRef, literal(1)), |
| le(bRef, literal(1)), |
| isNull(bRef)), |
| "true"); |
| |
| // "b <= 1 or b > 1 or b is null" ==> "true" |
| checkSimplifyFilter( |
| or(le(bRef, literal(1)), |
| gt(bRef, literal(1)), |
| isNull(bRef)), |
| "true"); |
| |
| // "b < 2 or b > 0 or b is null" ==> "true" |
| checkSimplifyFilter( |
| or(lt(bRef, literal(2)), |
| gt(bRef, literal(0)), |
| isNull(bRef)), |
| "true"); |
| |
| // "b is not null or c is null" unchanged, |
| // but "c is null" is moved to front |
| checkSimplifyFilter( |
| or(isNotNull(bRef), |
| isNull(cRef)), |
| "OR(IS NULL(?0.c), IS NOT NULL(?0.b))"); |
| |
| // "d is null or d is not false" => "d is null or d" |
| // (because after the first term we know that d cannot be null) |
| checkSimplifyFilter( |
| or(isNull(dRef), |
| isNotFalse(dRef)), |
| "OR(IS NULL(?0.d), ?0.d)"); |
| |
| // multiple predicates are handled correctly |
| checkSimplifyFilter( |
| and( |
| or(eq(bRef, literal(1)), |
| eq(bRef, literal(2))), |
| eq(bRef, literal(2)), |
| eq(aRef, literal(3)), |
| or(eq(aRef, literal(3)), |
| eq(aRef, literal(4)))), |
| "AND(=(?0.b, 2), =(?0.a, 3))"); |
| |
| checkSimplify3( |
| or(lt(vInt(), nullInt), |
| ne(literal(0), vInt())), |
| "OR(null, <>(0, ?0.int0))", |
| "<>(0, ?0.int0)", |
| "true"); |
| } |
| |
| @Test void testSimplifyRange() { |
| final RexNode aRef = input(tInt(), 0); |
| // ((0 < a and a <= 10) or a >= 15) and a <> 6 and a <> 12 |
| RexNode expr = and( |
| or( |
| and(lt(literal(0), aRef), |
| le(aRef, literal(10))), |
| ge(aRef, literal(15))), |
| ne(aRef, literal(6)), |
| ne(aRef, literal(12))); |
| final String simplified = |
| "SEARCH($0, Sarg[(0..6), (6..10], [15..+\u221e)])"; |
| final String expanded = "OR(AND(>($0, 0), <($0, 6)), AND(>($0, 6)," |
| + " <=($0, 10)), >=($0, 15))"; |
| checkSimplify(expr, simplified) |
| .expandedSearch(expanded); |
| } |
| |
| @Test void testSimplifyRange2() { |
| final RexNode aRef = input(tInt(true), 0); |
| // a is null or a >= 15 |
| RexNode expr = or(isNull(aRef), |
| ge(aRef, literal(15))); |
| checkSimplify(expr, "SEARCH($0, Sarg[[15..+\u221e); NULL AS TRUE])") |
| .expandedSearch("OR(IS NULL($0), >=($0, 15))"); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-4190">[CALCITE-4190] |
| * OR simplification incorrectly loses term</a>. */ |
| @Test void testSimplifyRange3() { |
| final RexNode aRef = input(tInt(true), 0); |
| // (0 < a and a <= 10) or a is null or (8 < a and a < 12) or a >= 15 |
| RexNode expr = or( |
| and(lt(literal(0), aRef), |
| le(aRef, literal(10))), |
| isNull(aRef), |
| and(lt(literal(8), aRef), |
| lt(aRef, literal(12))), |
| ge(aRef, literal(15))); |
| // [CALCITE-4190] causes "or a >= 15" to disappear from the simplified form. |
| final String simplified = |
| "SEARCH($0, Sarg[(0..12), [15..+\u221e); NULL AS TRUE])"; |
| final String expanded = |
| "OR(IS NULL($0), AND(>($0, 0), <($0, 12)), >=($0, 15))"; |
| checkSimplify(expr, simplified) |
| .expandedSearch(expanded); |
| } |
| |
| @Test void testSimplifyRange4() { |
| final RexNode aRef = input(tInt(true), 0); |
| // not (a = 3 or a = 5) |
| RexNode expr = not( |
| or(eq(aRef, literal(3)), |
| eq(aRef, literal(5)))); |
| final String expected = |
| "SEARCH($0, Sarg[(-\u221e..3), (3..5), (5..+\u221e)])"; |
| final String expanded = "AND(<>($0, 3), <>($0, 5))"; |
| checkSimplify(expr, expected) |
| .expandedSearch(expanded); |
| } |
| |
| @Test void testSimplifyRange5() { |
| final RexNode aRef = input(tInt(true), 0); |
| // not (a = 3 or a = 5) or a is null |
| RexNode expr = or( |
| not( |
| or(eq(aRef, literal(3)), |
| eq(aRef, literal(5)))), |
| isNull(aRef)); |
| final String simplified = |
| "SEARCH($0, Sarg[(-\u221e..3), (3..5), (5..+\u221e); NULL AS TRUE])"; |
| final String expanded = "OR(IS NULL($0), AND(<>($0, 3), <>($0, 5)))"; |
| checkSimplify(expr, simplified) |
| .expandedSearch(expanded); |
| } |
| |
| @Test void testSimplifyRange6() { |
| // An IS NULL condition would not usually become a Sarg, |
| // but here it is combined with another condition, and together they cross |
| // the complexity threshold. |
| final RexNode aRef = input(tInt(true), 0); |
| final RexNode bRef = input(tInt(true), 1); |
| // a in (1, 2) or b is null |
| RexNode expr = or(eq(aRef, literal(1)), eq(aRef, literal(2)), isNull(bRef)); |
| final String simplified = |
| "OR(IS NULL($1), SEARCH($0, Sarg[1, 2]))"; |
| final String expanded = "OR(IS NULL($1), =($0, 1), =($0, 2))"; |
| checkSimplify(expr, simplified) |
| .expandedSearch(expanded); |
| } |
| |
| @Test void testSimplifyRange7() { |
| final RexNode aRef = input(tInt(true), 0); |
| // a is not null and a > 3 and a < 10 |
| RexNode expr = and( |
| isNotNull(aRef), |
| gt(aRef, literal(3)), |
| lt(aRef, literal(10))); |
| final String simplified = "SEARCH($0, Sarg[(3..10); NULL AS FALSE])"; |
| final String expanded = "AND(IS NOT NULL($0), AND(>($0, 3), <($0, 10)))"; |
| checkSimplify(expr, simplified) |
| .expandedSearch(expanded); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-4352">[CALCITE-4352] |
| * OR simplification incorrectly loses term</a>. */ |
| @Test void testSimplifyAndIsNotNull() { |
| final RexNode aRef = input(tInt(true), 0); |
| final RexNode bRef = input(tInt(true), 1); |
| // (0 < a and a < 10) and b is not null |
| RexNode expr = and( |
| and(lt(literal(0), aRef), |
| lt(aRef, literal(10))), |
| isNotNull(bRef)); |
| // [CALCITE-4352] causes "and b is not null" to disappear from the expanded |
| // form. |
| final String simplified = "AND(SEARCH($0, Sarg[(0..10)]), IS NOT NULL($1))"; |
| final String expanded = "AND(>($0, 0), <($0, 10), IS NOT NULL($1))"; |
| checkSimplify(expr, simplified) |
| .expandedSearch(expanded); |
| } |
| |
| @Test void testSimplifyAndIsNull() { |
| final RexNode aRef = input(tInt(true), 0); |
| final RexNode bRef = input(tInt(true), 1); |
| // (0 < a and a < 10) and b is null |
| RexNode expr = and( |
| and(lt(literal(0), aRef), |
| lt(aRef, literal(10))), |
| isNull(bRef)); |
| // [CALCITE-4352] causes "and b is null" to disappear from the expanded |
| // form. |
| final String simplified = "AND(SEARCH($0, Sarg[(0..10)]), IS NULL($1))"; |
| final String expanded = "AND(>($0, 0), <($0, 10), IS NULL($1))"; |
| checkSimplify(expr, simplified) |
| .expandedSearch(expanded); |
| } |
| |
| @Test void testSimplifyItemRangeTerms() { |
| RexNode item = item(input(tArray(tInt()), 3), literal(1)); |
| // paranoid validation doesn't support array types, disable it for a moment |
| simplify = this.simplify.withParanoid(false); |
| // (a=1 or a=2 or (arr[1]>4 and arr[1]<3 and a=3)) => a=1 or a=2 |
| checkSimplifyFilter( |
| or( |
| eq(vInt(), literal(1)), |
| eq(vInt(), literal(2)), |
| and(gt(item, literal(4)), lt(item, literal(3)), |
| eq(vInt(), literal(3)))), |
| "SEARCH(?0.int0, Sarg[1, 2])"); |
| simplify = simplify.withParanoid(true); |
| } |
| |
| @Test void testSimplifyNotAnd() { |
| final RexNode e = or( |
| le( |
| vBool(1), |
| literal(true)), |
| eq( |
| literal(false), |
| eq(literal(false), vBool(1)))); |
| checkSimplify(e, "OR(<=(?0.bool1, true), ?0.bool1)"); |
| } |
| |
| @Test void testSimplifyNeOrIsNullAndEq() { |
| // (deptno <> 20 OR deptno IS NULL) AND deptno = 10 |
| // ==> |
| // deptno = 10 |
| final RexNode e = |
| and( |
| or(ne(vInt(), literal(20)), |
| isNull(vInt())), |
| eq(vInt(), literal(10))); |
| checkSimplify(e, "=(?0.int0, 10)"); |
| } |
| |
| @Test void testSimplifyEqOrIsNullAndEq() { |
| // (deptno = 20 OR deptno IS NULL) AND deptno = 10 |
| // ==> |
| // deptno <> deptno |
| final RexNode e = |
| and( |
| or(eq(vInt(), literal(20)), |
| isNull(vInt())), |
| eq(vInt(), literal(10))); |
| checkSimplify3(e, "<>(?0.int0, ?0.int0)", "false", "IS NULL(?0.int0)"); |
| } |
| |
| @Test void testSimplifyEqOrIsNullAndEqSame() { |
| // (deptno = 10 OR deptno IS NULL) AND deptno = 10 |
| // ==> |
| // false |
| final RexNode e = |
| and( |
| or(eq(vInt(), literal(10)), |
| isNull(vInt())), |
| eq(vInt(), literal(10))); |
| checkSimplify(e, "=(?0.int0, 10)"); |
| } |
| |
| @Test void testSimplifyInAnd() { |
| // deptno in (20, 10) and deptno = 10 |
| // ==> |
| // deptno = 10 |
| checkSimplify( |
| and( |
| in(vInt(), literal(20), literal(10)), |
| eq(vInt(), literal(10))), |
| "=(?0.int0, 10)"); |
| |
| // deptno in (20, 10) and deptno = 30 |
| // ==> |
| // false |
| checkSimplify3( |
| and( |
| in(vInt(), literal(20), literal(10)), |
| eq(vInt(), literal(30))), |
| "<>(?0.int0, ?0.int0)", |
| "false", |
| "IS NULL(?0.int0)"); |
| } |
| |
| @Test void testSimplifyInOr() { |
| // deptno > 0 or deptno in (20, 10) |
| // ==> |
| // deptno > 0 |
| checkSimplify( |
| or( |
| gt(vInt(), literal(0)), |
| in(vInt(), literal(20), literal(10))), |
| ">(?0.int0, 0)"); |
| } |
| |
| /** Test strategies for {@code SargCollector.canMerge(Sarg, RexUnknownAs)}. */ |
| @Test void testSargMerge() { |
| checkSimplify3( |
| or(ne(vInt(), literal(1)), |
| eq(vInt(), literal(1))), |
| "OR(IS NOT NULL(?0.int0), null)", |
| "IS NOT NULL(?0.int0)", |
| "true"); |
| checkSimplify3( |
| and(gt(vInt(), literal(5)), |
| lt(vInt(), literal(3))), |
| "<>(?0.int0, ?0.int0)", |
| "false", |
| "IS NULL(?0.int0)"); |
| checkSimplify( |
| or(falseLiteral, |
| isNull(vInt())), |
| "IS NULL(?0.int0)"); |
| checkSimplify( |
| and(trueLiteral, |
| isNotNull(vInt())), |
| "IS NOT NULL(?0.int0)"); |
| } |
| |
| @Test void testSimplifyUnknown() { |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType rowType = typeFactory.builder() |
| .add("a", intType).nullable(true) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode aRef = rexBuilder.makeFieldAccess(range, 0); |
| |
| checkSimplify2( |
| and(eq(aRef, literal(1)), |
| nullInt), |
| "AND(=(?0.a, 1), null:INTEGER)", |
| "false"); |
| checkSimplify3( |
| and(trueLiteral, |
| nullBool), |
| "null:BOOLEAN", |
| "false", |
| "true"); |
| checkSimplify( |
| and(falseLiteral, |
| nullBool), |
| "false"); |
| |
| checkSimplify3( |
| and(nullBool, |
| eq(aRef, literal(1))), |
| "AND(null, =(?0.a, 1))", |
| "false", |
| "=(?0.a, 1)"); |
| |
| checkSimplify3( |
| or(eq(aRef, literal(1)), |
| nullBool), |
| "OR(=(?0.a, 1), null)", |
| "=(?0.a, 1)", |
| "true"); |
| checkSimplify( |
| or(trueLiteral, |
| nullBool), |
| "true"); |
| checkSimplify3( |
| or(falseLiteral, |
| nullBool), |
| "null:BOOLEAN", |
| "false", |
| "true"); |
| } |
| |
| @Test void testSimplifyAnd3() { |
| // in the case of 3-valued logic, the result must be unknown if a is unknown |
| checkSimplify2( |
| and(vBool(), not(vBool())), |
| "AND(null, IS NULL(?0.bool0))", |
| "false"); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-2840">[CALCITE-2840] |
| * Simplification should use more specific UnknownAs modes during simplification</a>. */ |
| @Test void testNestedAndSimplification() { |
| // to have the correct mode for the AND at the bottom, |
| // both the OR and AND parent should retain the UnknownAs mode |
| checkSimplify( |
| and( |
| eq(vInt(2), literal(2)), |
| or( |
| eq(vInt(3), literal(3)), |
| and( |
| ge(vInt(), literal(1)), |
| le(vInt(), literal(1))))), |
| "AND(=(?0.int2, 2), OR(=(?0.int3, 3), =(?0.int0, 1)))"); |
| } |
| |
| @Test void fieldAccessEqualsHashCode() { |
| assertEquals(vBool(), vBool(), "vBool() instances should be equal"); |
| assertEquals(vBool().hashCode(), vBool().hashCode(), "vBool().hashCode()"); |
| assertNotSame(vBool(), vBool(), "vBool() is expected to produce new RexFieldAccess"); |
| assertNotEquals(vBool(0), vBool(1), "vBool(0) != vBool(1)"); |
| } |
| |
| @Test void testSimplifyDynamicParam() { |
| checkSimplify(or(vBool(), vBool()), "?0.bool0"); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-1289">[CALCITE-1289] |
| * RexUtil.simplifyCase() should account for nullability</a>. */ |
| @Test void testSimplifyCaseNotNullableBoolean() { |
| RexNode condition = eq(vVarchar(), literal("S")); |
| RexCall caseNode = (RexCall) case_(condition, trueLiteral, falseLiteral); |
| |
| final RexCall result = (RexCall) simplify.simplifyUnknownAs(caseNode, RexUnknownAs.UNKNOWN); |
| assertThat("The case should be nonNullable", caseNode.getType().isNullable(), is(false)); |
| assertThat("Expected a nonNullable type", result.getType().isNullable(), is(false)); |
| assertThat(result.getType().getSqlTypeName(), is(SqlTypeName.BOOLEAN)); |
| assertThat(result.getOperator(), is(SqlStdOperatorTable.IS_TRUE)); |
| assertThat(result.getOperands().get(0), is(condition)); |
| } |
| |
| @Test void testSimplifyCaseNullableBoolean() { |
| RexNode condition = eq(input(tVarchar(), 0), literal("S")); |
| RexNode caseNode = case_(condition, trueLiteral, falseLiteral); |
| |
| RexCall result = |
| (RexCall) simplify.simplifyUnknownAs(caseNode, RexUnknownAs.UNKNOWN); |
| assertThat(result.getType().isNullable(), is(false)); |
| assertThat(result.getType().getSqlTypeName(), is(SqlTypeName.BOOLEAN)); |
| assertThat(result, is(condition)); |
| } |
| |
| @Test void testSimplifyRecurseIntoArithmetics() { |
| checkSimplify( |
| plus(literal(1), |
| case_( |
| falseLiteral, literal(1), |
| trueLiteral, literal(2), |
| literal(3))), |
| "+(1, 2)"); |
| } |
| |
| @Test void testSimplifyCaseBranchesCollapse() { |
| // case when x is true then 1 when x is not true then 1 else 2 end |
| // => case when x is true or x is not true then 1 else 2 end |
| checkSimplify( |
| case_( |
| isTrue(vBool()), literal(1), |
| isNotTrue(vBool()), literal(1), |
| literal(2)), |
| "CASE(OR(?0.bool0, IS NOT TRUE(?0.bool0)), 1, 2)"); |
| } |
| |
| @Test void testSimplifyCaseBranchesCollapse2() { |
| // case when x is true then 1 when true then 1 else 2 end |
| // => 1 |
| checkSimplify( |
| case_( |
| isTrue(vBool()), literal(1), |
| trueLiteral, literal(1), |
| literal(2)), |
| "1"); |
| } |
| |
| @Test void testSimplifyCaseNullableVarChar() { |
| RexNode condition = eq(input(tVarchar(), 0), literal("S")); |
| RexNode caseNode = case_(condition, literal("A"), literal("B")); |
| |
| RexCall result = |
| (RexCall) simplify.simplifyUnknownAs(caseNode, RexUnknownAs.UNKNOWN); |
| assertThat(result.getType().isNullable(), is(false)); |
| assertThat(result.getType().getSqlTypeName(), is(SqlTypeName.CHAR)); |
| assertThat(result, is(caseNode)); |
| } |
| |
| @Test void testSimplifyCaseCasting() { |
| RexNode caseNode = case_(eq(vIntNotNull(), literal(3)), nullBool, falseLiteral); |
| |
| checkSimplify3(caseNode, "AND(=(?0.notNullInt0, 3), null)", |
| "false", |
| "=(?0.notNullInt0, 3)"); |
| } |
| |
| @Test void testSimplifyCaseAndNotSimplificationIsInAction() { |
| RexNode caseNode = case_( |
| eq(vIntNotNull(), literal(0)), falseLiteral, |
| eq(vIntNotNull(), literal(1)), trueLiteral, |
| falseLiteral); |
| checkSimplify(caseNode, "=(?0.notNullInt0, 1)"); |
| } |
| |
| @Test void testSimplifyCaseBranchRemovalStrengthensType() { |
| RexNode caseNode = |
| case_(falseLiteral, nullBool, eq(div(vInt(), literal(2)), literal(3)), trueLiteral, |
| falseLiteral); |
| assertThat("Expected to have a nullable type for " + caseNode + ".", |
| caseNode.getType().isNullable(), is(true)); |
| RexNode res = simplify.simplify(caseNode); |
| assertThat("Expected to have a nonNullable type for " + res + ".", |
| res.getType().isNullable(), is(false)); |
| } |
| |
| @Test void testSimplifyCaseCompaction() { |
| RexNode caseNode = case_(vBool(0), vInt(0), vBool(1), vInt(0), vInt(1)); |
| checkSimplify(caseNode, "CASE(OR(?0.bool0, ?0.bool1), ?0.int0, ?0.int1)"); |
| } |
| |
| @Test void testSimplifyCaseCompaction2() { |
| RexNode caseNode = case_(vBool(0), vInt(0), vBool(1), vInt(1), vInt(1)); |
| checkSimplify(caseNode, "CASE(?0.bool0, ?0.int0, ?0.int1)"); |
| } |
| |
| @Test void testSimplifyCaseCompactionDiv() { |
| // FIXME: RexInterpreter currently evaluates children beforehand. |
| simplify = simplify.withParanoid(false); |
| RexNode caseNode = case_(vBool(0), vInt(0), |
| eq(div(literal(3), vIntNotNull()), literal(11)), vInt(0), |
| vInt(1)); |
| // expectation here is that the 2 branches are not merged. |
| checkSimplifyUnchanged(caseNode); |
| } |
| |
| /** Tests a CASE value branch that contains division. */ |
| @Test void testSimplifyCaseDiv1() { |
| // FIXME: RexInterpreter currently evaluates children beforehand. |
| simplify = simplify.withParanoid(false); |
| RexNode caseNode = case_( |
| ne(vIntNotNull(), literal(0)), |
| eq(div(literal(3), vIntNotNull()), literal(11)), |
| falseLiteral); |
| checkSimplifyUnchanged(caseNode); |
| } |
| |
| /** Tests a CASE condition that contains division. */ |
| @Test void testSimplifyCaseDiv2() { |
| // FIXME: RexInterpreter currently evaluates children beforehand. |
| simplify = simplify.withParanoid(false); |
| RexNode caseNode = case_( |
| eq(vIntNotNull(), literal(0)), trueLiteral, |
| gt(div(literal(3), vIntNotNull()), literal(1)), trueLiteral, |
| falseLiteral); |
| checkSimplifyUnchanged(caseNode); |
| } |
| |
| @Test void testSimplifyCaseFirstBranchIsSafe() { |
| RexNode caseNode = case_( |
| gt(div(vIntNotNull(), literal(1)), literal(1)), falseLiteral, |
| trueLiteral); |
| checkSimplify(caseNode, "<=(/(?0.notNullInt0, 1), 1)"); |
| } |
| |
| @Test void testPushNotIntoCase() { |
| checkSimplify( |
| not( |
| case_( |
| isTrue(vBool()), vBool(1), |
| gt(div(vIntNotNull(), literal(2)), literal(1)), vBool(2), |
| vBool(3))), |
| "CASE(?0.bool0, NOT(?0.bool1), >(/(?0.notNullInt0, 2), 1), NOT(?0.bool2), NOT(?0.bool3))"); |
| } |
| |
| @Test void testNotRecursion() { |
| checkSimplify( |
| not(coalesce(nullBool, trueLiteral)), |
| "false"); |
| } |
| |
| @Test void testSimplifyAnd() { |
| RelDataType booleanNotNullableType = |
| typeFactory.createTypeWithNullability( |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN), false); |
| RelDataType booleanNullableType = |
| typeFactory.createTypeWithNullability( |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN), true); |
| RexNode andCondition = |
| and(rexBuilder.makeInputRef(booleanNotNullableType, 0), |
| rexBuilder.makeInputRef(booleanNullableType, 1), |
| rexBuilder.makeInputRef(booleanNotNullableType, 2)); |
| RexNode result = |
| simplify.simplifyUnknownAs(andCondition, RexUnknownAs.UNKNOWN); |
| assertThat(result.getType().isNullable(), is(true)); |
| assertThat(result.getType().getSqlTypeName(), is(SqlTypeName.BOOLEAN)); |
| } |
| |
| @Test void testSimplifyIsNotNull() { |
| RelDataType intType = |
| typeFactory.createTypeWithNullability( |
| typeFactory.createSqlType(SqlTypeName.INTEGER), false); |
| RelDataType intNullableType = |
| typeFactory.createTypeWithNullability( |
| typeFactory.createSqlType(SqlTypeName.INTEGER), true); |
| final RexInputRef i0 = rexBuilder.makeInputRef(intNullableType, 0); |
| final RexInputRef i1 = rexBuilder.makeInputRef(intNullableType, 1); |
| final RexInputRef i2 = rexBuilder.makeInputRef(intType, 2); |
| final RexInputRef i3 = rexBuilder.makeInputRef(intType, 3); |
| final RexLiteral null_ = rexBuilder.makeNullLiteral(intType); |
| checkSimplify(isNotNull(lt(i0, i1)), |
| "AND(IS NOT NULL($0), IS NOT NULL($1))"); |
| checkSimplify(isNotNull(lt(i0, i2)), "IS NOT NULL($0)"); |
| checkSimplify(isNotNull(lt(i2, i3)), "true"); |
| checkSimplify(isNotNull(lt(i0, literal(1))), "IS NOT NULL($0)"); |
| checkSimplify(isNotNull(lt(i0, null_)), "false"); |
| // test simplify operand of case when expression |
| checkSimplify( |
| isNull(case_(falseLiteral, unaryPlus(i0), literal(-1))), |
| "false"); |
| checkSimplify( |
| isNull(case_(trueLiteral, unaryPlus(i0), literal(-1))), |
| "IS NULL($0)"); |
| checkSimplify( |
| isNotNull(case_(falseLiteral, unaryPlus(i0), literal(-1))), |
| "true"); |
| checkSimplify( |
| isNotNull(case_(trueLiteral, unaryPlus(i0), literal(-1))), |
| "IS NOT NULL($0)"); |
| // test simplify operand of redundant cast |
| checkSimplify(isNull(cast(i2, intType)), "false"); |
| checkSimplify(isNotNull(cast(i2, intType)), "true"); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-2929">[CALCITE-2929] |
| * Simplification of IS NULL checks are incorrectly assuming that CAST-s are possible</a>. */ |
| @Test void testSimplifyCastIsNull() { |
| checkSimplifyUnchanged(isNull(cast(vVarchar(), tInt(true)))); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-2929">[CALCITE-2929] |
| * Simplification of IS NULL checks are incorrectly assuming that CAST-s are possible</a>. */ |
| @Test void testSimplifyCastIsNull2() { |
| checkSimplifyUnchanged(isNull(cast(vVarcharNotNull(), tInt(false)))); |
| } |
| |
| @Test void checkSimplifyDynamicParam() { |
| checkSimplify(isNotNull(lt(vInt(0), vInt(1))), |
| "AND(IS NOT NULL(?0.int0), IS NOT NULL(?0.int1))"); |
| checkSimplify(isNotNull(lt(vInt(0), vIntNotNull(2))), |
| "IS NOT NULL(?0.int0)"); |
| checkSimplify(isNotNull(lt(vIntNotNull(2), vIntNotNull(3))), "true"); |
| checkSimplify(isNotNull(lt(vInt(0), literal(BigDecimal.ONE))), |
| "IS NOT NULL(?0.int0)"); |
| checkSimplify(isNotNull(lt(vInt(0), null_(tInt()))), "false"); |
| } |
| |
| @Test void testSimplifyCastLiteral() { |
| final List<RexLiteral> literals = new ArrayList<>(); |
| literals.add( |
| rexBuilder.makeExactLiteral(BigDecimal.ONE, |
| typeFactory.createSqlType(SqlTypeName.INTEGER))); |
| literals.add( |
| rexBuilder.makeExactLiteral(BigDecimal.valueOf(2), |
| typeFactory.createSqlType(SqlTypeName.BIGINT))); |
| literals.add( |
| rexBuilder.makeExactLiteral(BigDecimal.valueOf(3), |
| typeFactory.createSqlType(SqlTypeName.SMALLINT))); |
| literals.add( |
| rexBuilder.makeExactLiteral(BigDecimal.valueOf(4), |
| typeFactory.createSqlType(SqlTypeName.TINYINT))); |
| literals.add( |
| rexBuilder.makeExactLiteral(new BigDecimal("1234"), |
| typeFactory.createSqlType(SqlTypeName.DECIMAL, 4, 0))); |
| literals.add( |
| rexBuilder.makeExactLiteral(new BigDecimal("123.45"), |
| typeFactory.createSqlType(SqlTypeName.DECIMAL, 5, 2))); |
| literals.add( |
| rexBuilder.makeApproxLiteral(new BigDecimal("3.1415"), |
| typeFactory.createSqlType(SqlTypeName.REAL))); |
| literals.add( |
| rexBuilder.makeApproxLiteral(BigDecimal.valueOf(Math.E), |
| typeFactory.createSqlType(SqlTypeName.FLOAT))); |
| literals.add( |
| rexBuilder.makeApproxLiteral(BigDecimal.valueOf(Math.PI), |
| typeFactory.createSqlType(SqlTypeName.DOUBLE))); |
| literals.add(rexBuilder.makeLiteral(true)); |
| literals.add(rexBuilder.makeLiteral(false)); |
| literals.add(rexBuilder.makeLiteral("hello world")); |
| literals.add(rexBuilder.makeLiteral("1969-07-20 12:34:56")); |
| literals.add(rexBuilder.makeLiteral("1969-07-20")); |
| literals.add(rexBuilder.makeLiteral("12:34:45")); |
| literals.add( |
| rexBuilder.makeLiteral(new ByteString(new byte[] {1, 2, -34, 0, -128}), |
| typeFactory.createSqlType(SqlTypeName.BINARY, 5))); |
| literals.add(rexBuilder.makeDateLiteral(new DateString(1974, 8, 9))); |
| literals.add(rexBuilder.makeTimeLiteral(new TimeString(1, 23, 45), 0)); |
| literals.add( |
| rexBuilder.makeTimestampLiteral( |
| new TimestampString(1974, 8, 9, 1, 23, 45), 0)); |
| |
| final Multimap<SqlTypeName, RexLiteral> map = LinkedHashMultimap.create(); |
| for (RexLiteral literal : literals) { |
| map.put(literal.getTypeName(), literal); |
| } |
| |
| final List<RelDataType> types = new ArrayList<>(); |
| types.add(typeFactory.createSqlType(SqlTypeName.INTEGER)); |
| types.add(typeFactory.createSqlType(SqlTypeName.BIGINT)); |
| types.add(typeFactory.createSqlType(SqlTypeName.SMALLINT)); |
| types.add(typeFactory.createSqlType(SqlTypeName.TINYINT)); |
| types.add(typeFactory.createSqlType(SqlTypeName.REAL)); |
| types.add(typeFactory.createSqlType(SqlTypeName.FLOAT)); |
| types.add(typeFactory.createSqlType(SqlTypeName.DOUBLE)); |
| types.add(typeFactory.createSqlType(SqlTypeName.BOOLEAN)); |
| types.add(typeFactory.createSqlType(SqlTypeName.VARCHAR, 10)); |
| types.add(typeFactory.createSqlType(SqlTypeName.CHAR, 5)); |
| types.add(typeFactory.createSqlType(SqlTypeName.VARBINARY, 60)); |
| types.add(typeFactory.createSqlType(SqlTypeName.BINARY, 3)); |
| types.add(typeFactory.createSqlType(SqlTypeName.TIMESTAMP)); |
| types.add(typeFactory.createSqlType(SqlTypeName.TIME)); |
| types.add(typeFactory.createSqlType(SqlTypeName.DATE)); |
| |
| for (RelDataType fromType : types) { |
| for (RelDataType toType : types) { |
| if (SqlTypeAssignmentRule.instance() |
| .canApplyFrom(toType.getSqlTypeName(), fromType.getSqlTypeName())) { |
| for (RexLiteral literal : map.get(fromType.getSqlTypeName())) { |
| final RexNode cast = rexBuilder.makeCast(toType, literal); |
| if (cast instanceof RexLiteral) { |
| assertThat(cast.getType(), is(toType)); |
| continue; // makeCast already simplified |
| } |
| final RexNode simplified = |
| simplify.simplifyUnknownAs(cast, RexUnknownAs.UNKNOWN); |
| boolean expectedSimplify = |
| literal.getTypeName() != toType.getSqlTypeName() |
| || (literal.getTypeName() == SqlTypeName.CHAR |
| && ((NlsString) literal.getValue()).getValue().length() |
| > toType.getPrecision()) |
| || (literal.getTypeName() == SqlTypeName.BINARY |
| && ((ByteString) literal.getValue()).length() |
| > toType.getPrecision()); |
| boolean couldSimplify = !cast.equals(simplified); |
| final String reason = (expectedSimplify |
| ? "expected to simplify, but could not: " |
| : "simplified, but did not expect to: ") |
| + cast + " --> " + simplified; |
| assertThat(reason, couldSimplify, is(expectedSimplify)); |
| } |
| } |
| } |
| } |
| } |
| |
| @Test void testCastLiteral() { |
| assertNode("cast(literal int not null)", |
| "42:INTEGER NOT NULL", cast(literal(42), tInt())); |
| assertNode("cast(literal int)", |
| "42:INTEGER NOT NULL", cast(literal(42), nullable(tInt()))); |
| |
| assertNode("abstractCast(literal int not null)", |
| "CAST(42):INTEGER NOT NULL", abstractCast(literal(42), tInt())); |
| assertNode("abstractCast(literal int)", |
| "CAST(42):INTEGER", abstractCast(literal(42), nullable(tInt()))); |
| } |
| |
| @Test void testSimplifyCastLiteral2() { |
| final RexLiteral literalAbc = rexBuilder.makeLiteral("abc"); |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType varcharType = |
| typeFactory.createSqlType(SqlTypeName.VARCHAR, 10); |
| final RelDataType booleanType = |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN); |
| final RelDataType dateType = typeFactory.createSqlType(SqlTypeName.DATE); |
| final RelDataType timestampType = |
| typeFactory.createSqlType(SqlTypeName.TIMESTAMP); |
| checkSimplifyUnchanged(cast(literalAbc, intType)); |
| checkSimplifyUnchanged(cast(literal(1), intType)); |
| checkSimplifyUnchanged(cast(literalAbc, varcharType)); |
| checkSimplify(cast(literal(1), varcharType), "'1':VARCHAR(10)"); |
| checkSimplifyUnchanged(cast(literalAbc, booleanType)); |
| checkSimplify(cast(literal(1), booleanType), |
| "false"); // different from Hive |
| checkSimplifyUnchanged(cast(literalAbc, dateType)); |
| checkSimplify(cast(literal(1), dateType), |
| "1970-01-02"); // different from Hive |
| checkSimplifyUnchanged(cast(literalAbc, timestampType)); |
| checkSimplify(cast(literal(1), timestampType), |
| "1970-01-01 00:00:00"); // different from Hive |
| } |
| |
| @Test void testSimplifyCastLiteral3() { |
| // Default TimeZone is "America/Los_Angeles" (DummyDataContext) |
| final RexLiteral literalDate = rexBuilder.makeDateLiteral(new DateString("2011-07-20")); |
| final RexLiteral literalTime = rexBuilder.makeTimeLiteral(new TimeString("12:34:56"), 0); |
| final RexLiteral literalTimestamp = rexBuilder.makeTimestampLiteral( |
| new TimestampString("2011-07-20 12:34:56"), 0); |
| final RexLiteral literalTimeLTZ = |
| rexBuilder.makeTimeWithLocalTimeZoneLiteral( |
| new TimeString(1, 23, 45), 0); |
| final RexLiteral timeLTZChar1 = rexBuilder.makeLiteral("12:34:45 America/Los_Angeles"); |
| final RexLiteral timeLTZChar2 = rexBuilder.makeLiteral("12:34:45 UTC"); |
| final RexLiteral timeLTZChar3 = rexBuilder.makeLiteral("12:34:45 GMT+01"); |
| final RexLiteral timestampLTZChar1 = rexBuilder.makeLiteral("2011-07-20 12:34:56 Asia/Tokyo"); |
| final RexLiteral timestampLTZChar2 = rexBuilder.makeLiteral("2011-07-20 12:34:56 GMT+01"); |
| final RexLiteral timestampLTZChar3 = rexBuilder.makeLiteral("2011-07-20 12:34:56 UTC"); |
| final RexLiteral literalTimestampLTZ = |
| rexBuilder.makeTimestampWithLocalTimeZoneLiteral( |
| new TimestampString(2011, 7, 20, 8, 23, 45), 0); |
| |
| final RelDataType dateType = |
| typeFactory.createSqlType(SqlTypeName.DATE); |
| final RelDataType timeType = |
| typeFactory.createSqlType(SqlTypeName.TIME); |
| final RelDataType timestampType = |
| typeFactory.createSqlType(SqlTypeName.TIMESTAMP); |
| final RelDataType timeLTZType = |
| typeFactory.createSqlType(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE); |
| final RelDataType timestampLTZType = |
| typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); |
| final RelDataType varCharType = |
| typeFactory.createSqlType(SqlTypeName.VARCHAR, 40); |
| |
| checkSimplify(cast(timeLTZChar1, timeLTZType), |
| "20:34:45:TIME_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplify(cast(timeLTZChar2, timeLTZType), |
| "12:34:45:TIME_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplify(cast(timeLTZChar3, timeLTZType), |
| "11:34:45:TIME_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplifyUnchanged(cast(literalTimeLTZ, timeLTZType)); |
| checkSimplify(cast(timestampLTZChar1, timestampLTZType), |
| "2011-07-20 03:34:56:TIMESTAMP_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplify(cast(timestampLTZChar2, timestampLTZType), |
| "2011-07-20 11:34:56:TIMESTAMP_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplify(cast(timestampLTZChar3, timestampLTZType), |
| "2011-07-20 12:34:56:TIMESTAMP_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplifyUnchanged(cast(literalTimestampLTZ, timestampLTZType)); |
| checkSimplify(cast(literalDate, timestampLTZType), |
| "2011-07-20 07:00:00:TIMESTAMP_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplify(cast(literalTime, timestampLTZType), |
| "2011-07-20 19:34:56:TIMESTAMP_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplify(cast(literalTimestamp, timestampLTZType), |
| "2011-07-20 19:34:56:TIMESTAMP_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplify(cast(literalTimestamp, dateType), |
| "2011-07-20"); |
| checkSimplify(cast(literalTimestampLTZ, dateType), |
| "2011-07-20"); |
| checkSimplify(cast(literalTimestampLTZ, timeType), |
| "01:23:45"); |
| checkSimplify(cast(literalTimestampLTZ, timestampType), |
| "2011-07-20 01:23:45"); |
| checkSimplify(cast(literalTimeLTZ, timeType), |
| "17:23:45"); |
| checkSimplify(cast(literalTime, timeLTZType), |
| "20:34:56:TIME_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplify(cast(literalTimestampLTZ, timeLTZType), |
| "08:23:45:TIME_WITH_LOCAL_TIME_ZONE(0)"); |
| checkSimplify(cast(literalTimeLTZ, varCharType), |
| "'17:23:45 America/Los_Angeles':VARCHAR(40)"); |
| checkSimplify(cast(literalTimestampLTZ, varCharType), |
| "'2011-07-20 01:23:45 America/Los_Angeles':VARCHAR(40)"); |
| checkSimplify(cast(literalTimeLTZ, timestampType), |
| "2011-07-19 18:23:45"); |
| checkSimplify(cast(literalTimeLTZ, timestampLTZType), |
| "2011-07-20 01:23:45:TIMESTAMP_WITH_LOCAL_TIME_ZONE(0)"); |
| } |
| |
| @Test void testRemovalOfNullabilityWideningCast() { |
| RexNode expr = cast(isTrue(vBoolNotNull()), tBool(true)); |
| assertThat(expr.getType().isNullable(), is(true)); |
| RexNode result = simplify.simplifyUnknownAs(expr, RexUnknownAs.UNKNOWN); |
| assertThat(result.getType().isNullable(), is(false)); |
| } |
| |
| @Test void testCompareTimestampWithTimeZone() { |
| final TimestampWithTimeZoneString timestampLTZChar1 = |
| new TimestampWithTimeZoneString("2011-07-20 10:34:56 America/Los_Angeles"); |
| final TimestampWithTimeZoneString timestampLTZChar2 = |
| new TimestampWithTimeZoneString("2011-07-20 19:34:56 Europe/Rome"); |
| final TimestampWithTimeZoneString timestampLTZChar3 = |
| new TimestampWithTimeZoneString("2011-07-20 01:34:56 Asia/Tokyo"); |
| final TimestampWithTimeZoneString timestampLTZChar4 = |
| new TimestampWithTimeZoneString("2011-07-20 10:34:56 America/Los_Angeles"); |
| |
| assertThat(timestampLTZChar1.equals(timestampLTZChar2), is(false)); |
| assertThat(timestampLTZChar1.equals(timestampLTZChar3), is(false)); |
| assertThat(timestampLTZChar1.equals(timestampLTZChar4), is(true)); |
| } |
| |
| @Test void testSimplifyLiterals() { |
| final RexLiteral literalAbc = rexBuilder.makeLiteral("abc"); |
| final RexLiteral literalDef = rexBuilder.makeLiteral("def"); |
| final RexLiteral literalOneDotZero = |
| rexBuilder.makeExactLiteral(new BigDecimal(1D)); |
| |
| // 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(literal(0), literal(1)), "false"); |
| checkSimplify(eq(literal(1), literal(0)), "false"); |
| checkSimplify(ne(literal(0), literal(1)), "true"); |
| checkSimplify(ne(literal(1), literal(0)), "true"); |
| checkSimplify(gt(literal(0), literal(1)), "false"); |
| checkSimplify(gt(literal(1), literal(0)), "true"); |
| checkSimplify(gt(literal(1), literal(1)), "false"); |
| checkSimplify(ge(literal(0), literal(1)), "false"); |
| checkSimplify(ge(literal(1), literal(0)), "true"); |
| checkSimplify(ge(literal(1), literal(1)), "true"); |
| checkSimplify(lt(literal(0), literal(1)), "true"); |
| checkSimplify(lt(literal(1), literal(0)), "false"); |
| checkSimplify(lt(literal(1), literal(1)), "false"); |
| checkSimplify(le(literal(0), literal(1)), "true"); |
| checkSimplify(le(literal(1), literal(0)), "false"); |
| checkSimplify(le(literal(1), literal(1)), "true"); |
| |
| // Check decimal equality comparison |
| checkSimplify(eq(literal(1), literalOneDotZero), "true"); |
| checkSimplify(eq(literalOneDotZero, literal(1)), "true"); |
| checkSimplify(ne(literal(1), literalOneDotZero), "false"); |
| checkSimplify(ne(literalOneDotZero, literal(1)), "false"); |
| |
| // Check different types shouldn't change simplification |
| checkSimplifyUnchanged(eq(literal(0), literalAbc)); |
| checkSimplifyUnchanged(eq(literalAbc, literal(0))); |
| checkSimplifyUnchanged(ne(literal(0), literalAbc)); |
| checkSimplifyUnchanged(ne(literalAbc, literal(0))); |
| checkSimplifyUnchanged(gt(literal(0), literalAbc)); |
| checkSimplifyUnchanged(gt(literalAbc, literal(0))); |
| checkSimplifyUnchanged(ge(literal(0), literalAbc)); |
| checkSimplifyUnchanged(ge(literalAbc, literal(0))); |
| checkSimplifyUnchanged(lt(literal(0), literalAbc)); |
| checkSimplifyUnchanged(lt(literalAbc, literal(0))); |
| checkSimplifyUnchanged(le(literal(0), literalAbc)); |
| checkSimplifyUnchanged(le(literalAbc, literal(0))); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-2421">[CALCITE-2421] |
| * RexSimplify#simplifyAnds foregoes some simplifications if unknownAsFalse |
| * set to true</a>. */ |
| @Test void testSelfComparisons() { |
| checkSimplify3(and(eq(vInt(), vInt()), eq(vInt(1), vInt(1))), |
| "AND(OR(null, IS NOT NULL(?0.int0)), OR(null, IS NOT NULL(?0.int1)))", |
| "AND(IS NOT NULL(?0.int0), IS NOT NULL(?0.int1))", |
| "true"); |
| checkSimplify3(and(ne(vInt(), vInt()), ne(vInt(1), vInt(1))), |
| "AND(null, IS NULL(?0.int0), IS NULL(?0.int1))", |
| "false", |
| "AND(IS NULL(?0.int0), IS NULL(?0.int1))"); |
| } |
| |
| @Test void testBooleanComparisons() { |
| checkSimplify(eq(vBool(), trueLiteral), "?0.bool0"); |
| checkSimplify(ge(vBool(), trueLiteral), "?0.bool0"); |
| checkSimplify(ne(vBool(), trueLiteral), "NOT(?0.bool0)"); |
| checkSimplify(lt(vBool(), trueLiteral), "NOT(?0.bool0)"); |
| |
| checkSimplifyUnchanged(gt(vBool(), trueLiteral)); |
| checkSimplifyUnchanged(le(vBool(), trueLiteral)); |
| checkSimplify(gt(vBoolNotNull(), trueLiteral), "false"); |
| checkSimplify(le(vBoolNotNull(), trueLiteral), "true"); |
| |
| checkSimplify(eq(vBool(), falseLiteral), "NOT(?0.bool0)"); |
| checkSimplify(ne(vBool(), falseLiteral), "?0.bool0"); |
| checkSimplify(gt(vBool(), falseLiteral), "?0.bool0"); |
| checkSimplify(le(vBool(), falseLiteral), "NOT(?0.bool0)"); |
| |
| checkSimplifyUnchanged(ge(vBool(), falseLiteral)); |
| checkSimplifyUnchanged(lt(vBool(), falseLiteral)); |
| |
| checkSimplify(ge(vBoolNotNull(), falseLiteral), "true"); |
| checkSimplify(lt(vBoolNotNull(), falseLiteral), "false"); |
| } |
| |
| @Test void testSimpleDynamicVars() { |
| assertTypeAndToString( |
| vBool(2), "?0.bool2", "BOOLEAN"); |
| assertTypeAndToString( |
| vBoolNotNull(0), "?0.notNullBool0", "BOOLEAN NOT NULL"); |
| |
| assertTypeAndToString( |
| vInt(2), "?0.int2", "INTEGER"); |
| assertTypeAndToString( |
| vIntNotNull(0), "?0.notNullInt0", "INTEGER NOT NULL"); |
| |
| assertTypeAndToString( |
| vVarchar(), "?0.varchar0", "VARCHAR"); |
| assertTypeAndToString( |
| vVarcharNotNull(9), "?0.notNullVarchar9", "VARCHAR NOT NULL"); |
| } |
| |
| private void assertTypeAndToString( |
| RexNode rexNode, String representation, String type) { |
| assertEquals(representation, rexNode.toString()); |
| assertEquals(type, rexNode.getType().toString() |
| + (rexNode.getType().isNullable() ? "" : " NOT NULL"), "type of " + rexNode); |
| } |
| |
| @Test void testIsDeterministic() { |
| SqlOperator ndc = new SqlSpecialOperator( |
| "NDC", |
| SqlKind.OTHER_FUNCTION, |
| 0, |
| false, |
| ReturnTypes.BOOLEAN, |
| null, null) { |
| @Override public boolean isDeterministic() { |
| return false; |
| } |
| }; |
| RexNode n = rexBuilder.makeCall(ndc); |
| assertFalse(RexUtil.isDeterministic(n)); |
| assertEquals(0, |
| RexUtil.retainDeterministic(RelOptUtil.conjunctions(n)).size()); |
| } |
| |
| @Test void testConstantMap() { |
| final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| final RelDataType bigintType = typeFactory.createSqlType(SqlTypeName.BIGINT); |
| final RelDataType decimalType = typeFactory.createSqlType(SqlTypeName.DECIMAL, 4, 2); |
| final RelDataType charType = typeFactory.createSqlType(SqlTypeName.CHAR, 5); |
| final RelDataType rowType = typeFactory.builder() |
| .add("a", intType) |
| .add("b", intType) |
| .add("c", intType) |
| .add("d", intType) |
| .add("e", bigintType) |
| .add("f", decimalType) |
| .add("g", charType) |
| .build(); |
| |
| final RexDynamicParam range = rexBuilder.makeDynamicParam(rowType, 0); |
| final RexNode aRef = rexBuilder.makeFieldAccess(range, 0); |
| final RexNode bRef = rexBuilder.makeFieldAccess(range, 1); |
| final RexNode cRef = rexBuilder.makeFieldAccess(range, 2); |
| final RexNode dRef = rexBuilder.makeFieldAccess(range, 3); |
| final RexNode eRef = rexBuilder.makeFieldAccess(range, 4); |
| final RexNode fRef = rexBuilder.makeFieldAccess(range, 5); |
| final RexNode gRef = rexBuilder.makeFieldAccess(range, 6); |
| |
| final ImmutableMap<RexNode, RexNode> map = |
| RexUtil.predicateConstants(RexNode.class, rexBuilder, |
| ImmutableList.of(eq(aRef, bRef), |
| eq(cRef, literal(1)), |
| eq(cRef, aRef), |
| eq(dRef, eRef))); |
| assertThat(getString(map), |
| is("{1=?0.c, ?0.a=?0.b, ?0.b=?0.a, ?0.c=1, ?0.d=?0.e, ?0.e=?0.d}")); |
| |
| // Contradictory constraints yield no constants |
| final RexNode ref0 = rexBuilder.makeInputRef(rowType, 0); |
| final ImmutableMap<RexNode, RexNode> map2 = |
| RexUtil.predicateConstants(RexNode.class, rexBuilder, |
| ImmutableList.of(eq(ref0, literal(1)), |
| eq(ref0, literal(2)))); |
| assertThat(getString(map2), is("{}")); |
| |
| // Contradictory constraints on field accesses SHOULD yield no constants |
| // but currently there's a bug |
| final ImmutableMap<RexNode, RexNode> map3 = |
| RexUtil.predicateConstants(RexNode.class, rexBuilder, |
| ImmutableList.of(eq(aRef, literal(1)), |
| eq(aRef, literal(2)))); |
| assertThat(getString(map3), is("{1=?0.a, 2=?0.a}")); |
| |
| // Different precision and scale in decimal |
| final ImmutableMap<RexNode, RexNode> map4 = |
| RexUtil.predicateConstants(RexNode.class, rexBuilder, |
| ImmutableList.of( |
| eq(cast(fRef, typeFactory.createSqlType(SqlTypeName.DECIMAL, 3, 1)), |
| rexBuilder.makeExactLiteral(BigDecimal.valueOf(21.2))))); |
| assertThat( |
| getString(map4), is("{21.2:DECIMAL(3, 1)=CAST(?0.f):DECIMAL(3, 1) NOT NULL," |
| + " CAST(?0.f):DECIMAL(3, 1) NOT NULL=21.2:DECIMAL(3, 1)}")); |
| |
| // Different precision in char |
| final ImmutableMap<RexNode, RexNode> map5 = |
| RexUtil.predicateConstants(RexNode.class, rexBuilder, |
| ImmutableList.of( |
| eq(cast(gRef, typeFactory.createSqlType(SqlTypeName.CHAR, 3)), |
| rexBuilder.makeLiteral("abc")))); |
| assertThat( |
| getString(map5), is("{'abc'=CAST(?0.g):CHAR(3) NOT NULL," |
| + " CAST(?0.g):CHAR(3) NOT NULL='abc'}")); |
| |
| // Cast bigint to int |
| final ImmutableMap<RexNode, RexNode> map6 = |
| RexUtil.predicateConstants(RexNode.class, rexBuilder, |
| ImmutableList.of( |
| eq(cast(eRef, typeFactory.createSqlType(SqlTypeName.INTEGER)), |
| literal(1)))); |
| assertThat( |
| getString(map6), is("{1=CAST(?0.e):INTEGER NOT NULL, CAST(?0.e):INTEGER NOT NULL=1}")); |
| |
| // Cast int to bigint |
| final ImmutableMap<RexNode, RexNode> map7 = |
| RexUtil.predicateConstants(RexNode.class, rexBuilder, |
| ImmutableList.of( |
| eq(cast(aRef, typeFactory.createSqlType(SqlTypeName.BIGINT)), |
| literal(1)))); |
| assertThat(getString(map7), is("{1=CAST(?0.a):BIGINT NOT NULL, ?0.a=1}")); |
| } |
| |
| @Test void notDistinct() { |
| checkSimplify( |
| isFalse(isNotDistinctFrom(vBool(0), vBool(1))), |
| "IS DISTINCT FROM(?0.bool0, ?0.bool1)"); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-2505">[CALCITE-2505] |
| * RexSimplify wrongly simplifies "COALESCE(+(NULL), x)" to "NULL"</a>. */ |
| @Test void testSimplifyCoalesce() { |
| // first arg not null |
| checkSimplify(coalesce(vIntNotNull(), vInt()), |
| "?0.notNullInt0"); |
| checkSimplifyUnchanged(coalesce(vInt(), vIntNotNull())); |
| // repeated arg |
| checkSimplify(coalesce(vInt(), vInt()), |
| "?0.int0"); |
| // repeated arg |
| checkSimplify(coalesce(vIntNotNull(), vIntNotNull()), |
| "?0.notNullInt0"); |
| checkSimplify(coalesce(vIntNotNull(), literal(1)), "?0.notNullInt0"); |
| checkSimplifyUnchanged(coalesce(vInt(), literal(1))); |
| checkSimplify( |
| coalesce(vInt(), plus(vInt(), vIntNotNull()), literal(1), |
| vIntNotNull()), |
| "COALESCE(?0.int0, +(?0.int0, ?0.notNullInt0), 1)"); |
| checkSimplify(coalesce(gt(nullInt, nullInt), trueLiteral), |
| "true"); |
| checkSimplify(coalesce(unaryPlus(nullInt), unaryPlus(vInt())), |
| "?0.int0"); |
| checkSimplifyUnchanged(coalesce(vInt(1), vInt())); |
| |
| checkSimplify(coalesce(nullInt, vInt()), "?0.int0"); |
| checkSimplify(coalesce(vInt(), nullInt, vInt(1)), |
| "COALESCE(?0.int0, ?0.int1)"); |
| |
| // first arg not null |
| checkSimplify(coalesce(vDecimalNotNull(), vDecimal()), |
| "?0.notNullDecimal0"); |
| checkSimplifyUnchanged(coalesce(vDecimal(), vDecimalNotNull())); |
| // repeated arg |
| checkSimplify(coalesce(vDecimal(), vDecimal()), |
| "?0.decimal0"); |
| // repeated arg |
| checkSimplify(coalesce(vDecimalNotNull(), vDecimalNotNull()), |
| "?0.notNullDecimal0"); |
| checkSimplify(coalesce(vDecimalNotNull(), literal(1)), "?0.notNullDecimal0"); |
| checkSimplifyUnchanged(coalesce(vDecimal(), literal(1))); |
| checkSimplify( |
| coalesce(vDecimal(), plus(vDecimal(), vDecimalNotNull()), literal(1), |
| vDecimalNotNull()), |
| "COALESCE(?0.decimal0, +(?0.decimal0, ?0.notNullDecimal0), 1)"); |
| checkSimplify(coalesce(gt(nullDecimal, nullDecimal), trueLiteral), |
| "true"); |
| checkSimplify(coalesce(unaryPlus(nullDecimal), unaryPlus(vDecimal())), |
| "?0.decimal0"); |
| checkSimplifyUnchanged(coalesce(vDecimal(1), vDecimal())); |
| |
| checkSimplify(coalesce(nullDecimal, vDecimal()), "?0.decimal0"); |
| checkSimplify(coalesce(vDecimal(), nullInt, vDecimal(1)), |
| "COALESCE(?0.decimal0, ?0.decimal1)"); |
| } |
| |
| @Test void simplifyNull() { |
| checkSimplify3(nullBool, "null:BOOLEAN", "false", "true"); |
| // null int must not be simplified to false |
| checkSimplifyUnchanged(nullInt); |
| } |
| |
| /** Converts a map to a string, sorting on the string representation of its |
| * keys. */ |
| private static String getString(ImmutableMap<RexNode, RexNode> map) { |
| final TreeMap<String, RexNode> map2 = new TreeMap<>(); |
| for (Map.Entry<RexNode, RexNode> entry : map.entrySet()) { |
| map2.put(entry.getKey().toString(), entry.getValue()); |
| } |
| return map2.toString(); |
| } |
| |
| @Test void testSimplifyFalse() { |
| final RelDataType booleanNullableType = |
| typeFactory.createTypeWithNullability( |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN), true); |
| final RexNode booleanInput = input(booleanNullableType, 0); |
| final RexNode isFalse = isFalse(booleanInput); |
| final RexCall result = (RexCall) simplify(isFalse); |
| assertThat(result.getType().isNullable(), is(false)); |
| assertThat(result.getOperator(), is(SqlStdOperatorTable.IS_FALSE)); |
| assertThat(result.getOperands().size(), is(1)); |
| assertThat(result.getOperands().get(0), is(booleanInput)); |
| |
| // Make sure that IS_FALSE(IS_FALSE(nullable boolean)) != IS_TRUE(nullable boolean) |
| // IS_FALSE(IS_FALSE(null)) = IS_FALSE(false) = true |
| // IS_TRUE(null) = false |
| final RexNode isFalseIsFalse = isFalse(isFalse); |
| final RexCall result2 = (RexCall) simplify(isFalseIsFalse); |
| assertThat(result2.getType().isNullable(), is(false)); |
| assertThat(result2.getOperator(), is(SqlStdOperatorTable.IS_NOT_FALSE)); |
| assertThat(result2.getOperands().size(), is(1)); |
| assertThat(result2.getOperands().get(0), is(booleanInput)); |
| } |
| |
| @Test void testSimplifyNot() { |
| // "NOT(NOT(x))" => "x" |
| checkSimplify(not(not(vBool())), "?0.bool0"); |
| // "NOT(true)" => "false" |
| checkSimplify(not(trueLiteral), "false"); |
| // "NOT(false)" => "true" |
| checkSimplify(not(falseLiteral), "true"); |
| // "NOT(IS FALSE(x))" => "IS NOT FALSE(x)" |
| checkSimplify3(not(isFalse(vBool())), |
| "IS NOT FALSE(?0.bool0)", "IS NOT FALSE(?0.bool0)", "?0.bool0"); |
| // "NOT(IS TRUE(x))" => "IS NOT TRUE(x)" |
| checkSimplify3(not(isTrue(vBool())), |
| "IS NOT TRUE(?0.bool0)", |
| "IS NOT TRUE(?0.bool0)", |
| "NOT(?0.bool0)"); |
| // "NOT(IS NULL(x))" => "IS NOT NULL(x)" |
| checkSimplify(not(isNull(vBool())), "IS NOT NULL(?0.bool0)"); |
| // "NOT(IS NOT NULL(x)) => "IS NULL(x)" |
| checkSimplify(not(isNotNull(vBool())), "IS NULL(?0.bool0)"); |
| // "NOT(AND(x0,x1))" => "OR(NOT(x0),NOT(x1))" |
| checkSimplify(not(and(vBool(0), vBool(1))), |
| "OR(NOT(?0.bool0), NOT(?0.bool1))"); |
| // "NOT(OR(x0,x1))" => "AND(NOT(x0),NOT(x1))" |
| checkSimplify(not(or(vBool(0), vBool(1))), |
| "AND(NOT(?0.bool0), NOT(?0.bool1))"); |
| } |
| |
| @Test void testSimplifyAndNot() { |
| // "x > 1 AND NOT (y > 2)" -> "x > 1 AND y <= 2" |
| checkSimplify(and(gt(vInt(1), literal(1)), not(gt(vInt(2), literal(2)))), |
| "AND(>(?0.int1, 1), <=(?0.int2, 2))"); |
| // "x = x AND NOT (y >= y)" |
| // -> "x = x AND y < y" (treating unknown as unknown) |
| // -> false (treating unknown as false) |
| checkSimplify3(and(eq(vInt(1), vInt(1)), not(ge(vInt(2), vInt(2)))), |
| "AND(OR(null, IS NOT NULL(?0.int1)), null, IS NULL(?0.int2))", |
| "false", |
| "IS NULL(?0.int2)"); |
| |
| // "NOT(x = x AND NOT (y = y))" |
| // -> "OR(x <> x, y >= y)" (treating unknown as unknown) |
| // -> "y IS NOT NULL" (treating unknown as false) |
| checkSimplify3(not(and(eq(vInt(1), vInt(1)), not(ge(vInt(2), vInt(2))))), |
| "OR(AND(null, IS NULL(?0.int1)), null, IS NOT NULL(?0.int2))", |
| "IS NOT NULL(?0.int2)", |
| "true"); |
| } |
| |
| @Test void testSimplifyOrIsNull() { |
| String expected = "SEARCH(?0.int0, Sarg[10; NULL AS TRUE])"; |
| // x = 10 OR x IS NULL |
| checkSimplify(or(eq(vInt(0), literal(10)), isNull(vInt(0))), expected); |
| // 10 = x OR x IS NULL |
| checkSimplify(or(eq(literal(10), vInt(0)), isNull(vInt(0))), expected); |
| } |
| |
| @Test void testSimplifyOrNot() { |
| // "x > 1 OR NOT (y > 2)" -> "x > 1 OR y <= 2" |
| checkSimplify(or(gt(vInt(1), literal(1)), not(gt(vInt(2), literal(2)))), |
| "OR(>(?0.int1, 1), <=(?0.int2, 2))"); |
| |
| // "x = x OR NOT (y >= y)" |
| // -> "x = x OR y < y" (treating unknown as unknown) |
| // -> "x IS NOT NULL" (treating unknown as false) |
| checkSimplify3(or(eq(vInt(1), vInt(1)), not(ge(vInt(2), vInt(2)))), |
| "OR(null, IS NOT NULL(?0.int1), AND(null, IS NULL(?0.int2)))", |
| "IS NOT NULL(?0.int1)", |
| "true"); |
| |
| // "NOT(x = x OR NOT (y = y))" |
| // -> "AND(x <> x, y >= y)" (treating unknown as unknown) |
| // -> "FALSE" (treating unknown as false) |
| checkSimplify3(not(or(eq(vInt(1), vInt(1)), not(ge(vInt(2), vInt(2))))), |
| "AND(null, IS NULL(?0.int1), OR(null, IS NOT NULL(?0.int2)))", |
| "false", |
| "IS NULL(?0.int1)"); |
| } |
| |
| private void checkSarg(String message, Sarg sarg, |
| Matcher<Integer> complexityMatcher, Matcher<String> stringMatcher) { |
| assertThat(message, sarg.complexity(), complexityMatcher); |
| assertThat(message, sarg.toString(), stringMatcher); |
| } |
| |
| /** Tests {@link Sarg#complexity()}. */ |
| @SuppressWarnings("UnstableApiUsage") |
| @Test void testSargComplexity() { |
| checkSarg("complexity of 'x is not null'", |
| Sarg.of(RexUnknownAs.FALSE, RangeSets.<Integer>rangeSetAll()), |
| is(1), is("Sarg[IS NOT NULL]")); |
| checkSarg("complexity of 'x is null'", |
| Sarg.of(RexUnknownAs.TRUE, ImmutableRangeSet.<Integer>of()), |
| is(1), is("Sarg[IS NULL]")); |
| checkSarg("complexity of 'false'", |
| Sarg.of(RexUnknownAs.FALSE, ImmutableRangeSet.<Integer>of()), |
| is(0), is("Sarg[FALSE]")); |
| checkSarg("complexity of 'true'", |
| Sarg.of(RexUnknownAs.TRUE, RangeSets.<Integer>rangeSetAll()), |
| is(2), is("Sarg[TRUE]")); |
| |
| checkSarg("complexity of 'x = 1'", |
| Sarg.of(RexUnknownAs.UNKNOWN, ImmutableRangeSet.of(Range.singleton(1))), |
| is(1), is("Sarg[1]")); |
| checkSarg("complexity of 'x > 1'", |
| Sarg.of(RexUnknownAs.UNKNOWN, |
| ImmutableRangeSet.of(Range.greaterThan(1))), |
| is(1), is("Sarg[(1..+\u221E)]")); |
| checkSarg("complexity of 'x >= 1'", |
| Sarg.of(RexUnknownAs.UNKNOWN, ImmutableRangeSet.of(Range.atLeast(1))), |
| is(1), is("Sarg[[1..+\u221E)]")); |
| checkSarg("complexity of 'x > 1 or x is null'", |
| Sarg.of(RexUnknownAs.TRUE, ImmutableRangeSet.of(Range.greaterThan(1))), |
| is(2), is("Sarg[(1..+\u221E); NULL AS TRUE]")); |
| checkSarg("complexity of 'x <> 1'", |
| Sarg.of(RexUnknownAs.UNKNOWN, |
| ImmutableRangeSet.of(Range.singleton(1)).complement()), |
| is(1), is("Sarg[(-\u221E..1), (1..+\u221E)]")); |
| checkSarg("complexity of 'x <> 1 or x is null'", |
| Sarg.of(RexUnknownAs.TRUE, |
| ImmutableRangeSet.of(Range.singleton(1)).complement()), |
| is(2), is("Sarg[(-\u221E..1), (1..+\u221E); NULL AS TRUE]")); |
| checkSarg("complexity of 'x < 10 or x >= 20'", |
| Sarg.of(RexUnknownAs.UNKNOWN, |
| ImmutableRangeSet.copyOf( |
| ImmutableList.of(Range.lessThan(10), Range.atLeast(20)))), |
| is(2), is("Sarg[(-\u221E..10), [20..+\u221E)]")); |
| checkSarg("complexity of 'x in (2, 4, 6) or x > 20'", |
| Sarg.of(RexUnknownAs.UNKNOWN, |
| ImmutableRangeSet.copyOf( |
| Arrays.asList(Range.singleton(2), Range.singleton(4), |
| Range.singleton(6), Range.greaterThan(20)))), |
| is(4), is("Sarg[2, 4, 6, (20..+\u221E)]")); |
| checkSarg("complexity of 'x between 3 and 8 or x between 10 and 20'", |
| Sarg.of(RexUnknownAs.UNKNOWN, |
| ImmutableRangeSet.copyOf( |
| Arrays.asList(Range.closed(3, 8), |
| Range.closed(10, 20)))), |
| is(2), is("Sarg[[3..8], [10..20]]")); |
| } |
| |
| @Test void testInterpreter() { |
| assertThat(eval(trueLiteral), is(true)); |
| assertThat(eval(nullInt), is(NullSentinel.INSTANCE)); |
| assertThat(eval(eq(nullInt, nullInt)), |
| is(NullSentinel.INSTANCE)); |
| assertThat(eval(eq(this.trueLiteral, nullInt)), |
| is(NullSentinel.INSTANCE)); |
| assertThat(eval(eq(falseLiteral, trueLiteral)), |
| is(false)); |
| assertThat(eval(ne(falseLiteral, trueLiteral)), |
| is(true)); |
| assertThat(eval(ne(falseLiteral, nullInt)), |
| is(NullSentinel.INSTANCE)); |
| assertThat(eval(and(this.trueLiteral, falseLiteral)), |
| is(false)); |
| } |
| |
| @Test void testIsNullRecursion() { |
| // make sure that simplification is visiting below isX expressions |
| checkSimplify( |
| isNull(or(coalesce(nullBool, trueLiteral), falseLiteral)), |
| "false"); |
| } |
| |
| @Test void testRedundantIsTrue() { |
| checkSimplify2( |
| isTrue(isTrue(vBool())), |
| "IS TRUE(?0.bool0)", |
| "?0.bool0"); |
| } |
| |
| @Test void testRedundantIsFalse() { |
| checkSimplify2( |
| isTrue(isFalse(vBool())), |
| "IS FALSE(?0.bool0)", |
| "NOT(?0.bool0)"); |
| } |
| |
| @Test void testRedundantIsNotTrue() { |
| checkSimplify3( |
| isNotFalse(isNotTrue(vBool())), |
| "IS NOT TRUE(?0.bool0)", |
| "IS NOT TRUE(?0.bool0)", |
| "NOT(?0.bool0)"); |
| } |
| |
| @Test void testRedundantIsNotFalse() { |
| checkSimplify3( |
| isNotFalse(isNotFalse(vBool())), |
| "IS NOT FALSE(?0.bool0)", |
| "IS NOT FALSE(?0.bool0)", |
| "?0.bool0"); |
| } |
| |
| /** Unit tests for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-2438">[CALCITE-2438] |
| * RexCall#isAlwaysTrue returns incorrect result</a>. */ |
| @Test void testIsAlwaysTrueAndFalseXisNullisNotNullisFalse() { |
| // "((x IS NULL) IS NOT NULL) IS FALSE" -> false |
| checkIs(isFalse(isNotNull(isNull(vBool()))), false); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseNotXisNullisNotNullisFalse() { |
| // "(NOT ((x IS NULL) IS NOT NULL)) IS FALSE" -> true |
| checkIs(isFalse(not(isNotNull(isNull(vBool())))), true); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseXisNullisNotNullisTrue() { |
| // "((x IS NULL) IS NOT NULL) IS TRUE" -> true |
| checkIs(isTrue(isNotNull(isNull(vBool()))), true); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseNotXisNullisNotNullisTrue() { |
| // "(NOT ((x IS NULL) IS NOT NULL)) IS TRUE" -> false |
| checkIs(isTrue(not(isNotNull(isNull(vBool())))), false); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseNotXisNullisNotNullisNotTrue() { |
| // "(NOT ((x IS NULL) IS NOT NULL)) IS NOT TRUE" -> true |
| checkIs(isNotTrue(not(isNotNull(isNull(vBool())))), true); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseXisNullisNotNull() { |
| // "(x IS NULL) IS NOT NULL" -> true |
| checkIs(isNotNull(isNull(vBool())), true); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseXisNotNullisNotNull() { |
| // "(x IS NOT NULL) IS NOT NULL" -> true |
| checkIs(isNotNull(isNotNull(vBool())), true); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseXisNullisNull() { |
| // "(x IS NULL) IS NULL" -> false |
| checkIs(isNull(isNull(vBool())), false); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseXisNotNullisNull() { |
| // "(x IS NOT NULL) IS NULL" -> false |
| checkIs(isNull(isNotNull(vBool())), false); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseXisNullisNotNullisNotFalse() { |
| // "((x IS NULL) IS NOT NULL) IS NOT FALSE" -> true |
| checkIs(isNotFalse(isNotNull(isNull(vBool()))), true); |
| } |
| |
| @Test void testIsAlwaysTrueAndFalseXisNullisNotNullisNotTrue() { |
| // "((x IS NULL) IS NOT NULL) IS NOT TRUE" -> false |
| checkIs(isNotTrue(isNotNull(isNull(vBool()))), false); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-2842">[CALCITE-2842] |
| * Computing digest of IN expressions leads to Exceptions</a>. */ |
| @Test void testInDigest() { |
| RexNode e = in(vInt(), literal(1), literal(2)); |
| assertThat(e.toString(), is("SEARCH(?0.int0, Sarg[1, 2])")); |
| } |
| |
| /** Tests that {@link #in} does not generate SEARCH if any of the arguments |
| * are not literals. */ |
| @Test void testInDigest2() { |
| RexNode e = in(vInt(0), literal(1), plus(literal(2), vInt(1))); |
| assertThat(e.toString(), |
| is("OR(=(?0.int0, 1), =(?0.int0, +(2, ?0.int1)))")); |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3192">[CALCITE-3192] |
| * Simplify OR incorrectly weaks condition</a>. */ |
| @Test void testOrSimplificationNotWeakensCondition() { |
| // "1 < a or (a < 3 and b = 2)" can't be simplified if a is nullable. |
| checkSimplifyUnchanged( |
| or( |
| lt(literal(1), vInt()), |
| and( |
| lt(vInt(), literal(3)), |
| vBoolNotNull(2)))); |
| } |
| |
| @Test void testIsNullSimplificationWithUnaryPlus() { |
| RexNode expr = |
| isNotNull(coalesce(unaryPlus(vInt(1)), vIntNotNull(0))); |
| RexNode s = simplify.simplifyUnknownAs(expr, RexUnknownAs.UNKNOWN); |
| |
| assertThat(expr.isAlwaysTrue(), is(true)); |
| assertThat(s, is(trueLiteral)); |
| } |
| |
| @Test void testIsNullSimplificationWithIsDistinctFrom() { |
| RexNode expr = |
| isNotNull( |
| case_(vBool(), |
| isDistinctFrom(falseLiteral, vBoolNotNull(0)), |
| vBoolNotNull(2))); |
| RexNode s = simplify.simplifyUnknownAs(expr, RexUnknownAs.UNKNOWN); |
| |
| assertThat(expr.isAlwaysTrue(), is(true)); |
| assertThat(s, is(trueLiteral)); |
| } |
| |
| @Test void testSimplifyCastUnaryMinus() { |
| RexNode expr = |
| isNull(ne(unaryMinus(cast(unaryMinus(vIntNotNull(1)), nullable(tInt()))), vIntNotNull(1))); |
| RexNode s = simplify.simplifyUnknownAs(expr, RexUnknownAs.UNKNOWN); |
| |
| assertThat(s, is(falseLiteral)); |
| } |
| |
| @Test void testSimplifyUnaryMinus() { |
| RexNode origExpr = vIntNotNull(1); |
| RexNode expr = unaryMinus(unaryMinus(origExpr)); |
| RexNode simplifiedExpr = simplify.simplifyUnknownAs(expr, RexUnknownAs.UNKNOWN); |
| assertThat(simplifiedExpr, is(origExpr)); |
| } |
| |
| @Test void testSimplifyUnaryPlus() { |
| RexNode origExpr = vIntNotNull(1); |
| RexNode expr = unaryPlus(origExpr); |
| RexNode simplifiedExpr = simplify.simplifyUnknownAs(expr, RexUnknownAs.UNKNOWN); |
| assertThat(simplifiedExpr, is(origExpr)); |
| } |
| |
| @Test void testSimplifyRangeWithMultiPredicates() { |
| final RexNode ref = input(tInt(), 0); |
| RelOptPredicateList relOptPredicateList = RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(gt(ref, literal(1)), le(ref, literal(5)))); |
| checkSimplifyFilter(gt(ref, literal(9)), relOptPredicateList, "false"); |
| } |
| |
| @Test void testSimplifyNotEqual() { |
| final RexNode ref = input(tInt(), 0); |
| RelOptPredicateList relOptPredicateList = RelOptPredicateList.of(rexBuilder, |
| ImmutableList.of(eq(ref, literal(9)))); |
| checkSimplifyFilter(ne(ref, literal(9)), relOptPredicateList, "false"); |
| checkSimplifyFilter(ne(ref, literal(5)), relOptPredicateList, "true"); |
| |
| final RexNode refNullable = input(tInt(true), 0); |
| checkSimplifyFilter(ne(refNullable, literal(9)), relOptPredicateList, |
| "false"); |
| checkSimplifyFilter(ne(refNullable, literal(5)), relOptPredicateList, |
| "IS NOT NULL($0)"); |
| } |
| |
| /** Tests |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-4094">[CALCITE-4094] |
| * RexSimplify should simplify more always true OR expressions</a>. */ |
| @Test void testSimplifyLike() { |
| final RexNode ref = input(tVarchar(true, 10), 0); |
| checkSimplify(like(ref, literal("%")), |
| "OR(null, IS NOT NULL($0))"); |
| checkSimplify(like(ref, literal("%"), literal("#")), |
| "OR(null, IS NOT NULL($0))"); |
| checkSimplify(or(isNull(ref), like(ref, literal("%"))), |
| "true"); |
| checkSimplify(or(isNull(ref), like(ref, literal("%"), literal("#"))), |
| "true"); |
| checkSimplifyUnchanged(like(ref, literal("%A"))); |
| checkSimplifyUnchanged(like(ref, literal("%A"), literal("#"))); |
| } |
| |
| @Test void testSimplifyNonDeterministicFunction() { |
| final SqlOperator ndc = new SqlSpecialOperator( |
| "NDC", |
| SqlKind.OTHER_FUNCTION, |
| 0, |
| false, |
| ReturnTypes.BOOLEAN, |
| null, null) { |
| @Override public boolean isDeterministic() { |
| return false; |
| } |
| }; |
| final RexNode call1 = rexBuilder.makeCall(ndc); |
| final RexNode call2 = rexBuilder.makeCall(ndc); |
| final RexNode expr = eq(call1, call2); |
| checkSimplifyUnchanged(expr); |
| } |
| |
| /** An operator that overrides the {@link #getStrongPolicyInference} |
| * method. */ |
| private static class SqlSpecialOperatorWithPolicy extends SqlSpecialOperator { |
| private final Strong.Policy policy; |
| private SqlSpecialOperatorWithPolicy(String name, SqlKind kind, int prec, boolean leftAssoc, |
| SqlReturnTypeInference returnTypeInference, SqlOperandTypeInference operandTypeInference, |
| SqlOperandTypeChecker operandTypeChecker, Strong.Policy policy) { |
| super(name, kind, prec, leftAssoc, returnTypeInference, operandTypeInference, |
| operandTypeChecker); |
| this.policy = policy; |
| } |
| @Override public Supplier<Strong.Policy> getStrongPolicyInference() { |
| return () -> policy; |
| } |
| } |
| |
| /** Unit test for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-4094">[CALCITE-4094] |
| * Allow SqlUserDefinedFunction to define an optional Strong.Policy</a>. */ |
| @Test void testSimplifyFunctionWithStrongPolicy() { |
| final SqlOperator op = new SqlSpecialOperator( |
| "OP1", |
| SqlKind.OTHER_FUNCTION, |
| 0, |
| false, |
| ReturnTypes.BOOLEAN, |
| null, |
| null) { |
| }; |
| // Operator with no Strong.Policy defined: no simplification can be made |
| checkSimplifyUnchanged(rexBuilder.makeCall(op, vInt())); |
| checkSimplifyUnchanged(rexBuilder.makeCall(op, vIntNotNull())); |
| checkSimplifyUnchanged(rexBuilder.makeCall(op, nullInt)); |
| |
| final SqlOperator opPolicyAsIs = new SqlSpecialOperatorWithPolicy( |
| "OP2", |
| SqlKind.OTHER_FUNCTION, |
| 0, |
| false, |
| ReturnTypes.BOOLEAN, |
| null, |
| null, |
| Strong.Policy.AS_IS) { |
| }; |
| // Operator with Strong.Policy.AS_IS: no simplification can be made |
| checkSimplifyUnchanged(rexBuilder.makeCall(opPolicyAsIs, vInt())); |
| checkSimplifyUnchanged(rexBuilder.makeCall(opPolicyAsIs, vIntNotNull())); |
| checkSimplifyUnchanged(rexBuilder.makeCall(opPolicyAsIs, nullInt)); |
| |
| final SqlOperator opPolicyAny = new SqlSpecialOperatorWithPolicy( |
| "OP3", |
| SqlKind.OTHER_FUNCTION, |
| 0, |
| false, |
| ReturnTypes.BOOLEAN, |
| null, |
| null, |
| Strong.Policy.ANY) { |
| }; |
| // Operator with Strong.Policy.ANY: simplification possible with null parameter |
| checkSimplifyUnchanged(rexBuilder.makeCall(opPolicyAny, vInt())); |
| checkSimplifyUnchanged(rexBuilder.makeCall(opPolicyAny, vIntNotNull())); |
| checkSimplify3(rexBuilder.makeCall(opPolicyAny, nullInt), "null:BOOLEAN", "false", "true"); |
| } |
| |
| @Test void testSimplifyVarbinary() { |
| checkSimplifyUnchanged(cast(cast(vInt(), tVarchar(true, 100)), tVarbinary(true))); |
| } |
| } |