This is an automated email from the ASF dual-hosted git repository.
jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/master by this push:
new 8581f0a [CALCITE-4562] Improve simplification of "x IS TRUE" and "x
LIKE '%'"
8581f0a is described below
commit 8581f0a3fe9a4f079cb4d36f02121ae22118714c
Author: Julian Hyde <[email protected]>
AuthorDate: Wed Mar 31 13:37:04 2021 -0700
[CALCITE-4562] Improve simplification of "x IS TRUE" and "x LIKE '%'"
Simplify "x LIKE '%'" to "x IS NOT NULL" in an unknown-as-false
context, such as a WHERE clause.
Simplify "x IS TRUE" to "x" if "x" is already NOT NULL;
similarly "IS FALSE", "IS NOT TRUE", "IS NOT FALSE".
---
.../org/apache/calcite/rex/RexInterpreter.java | 20 +++
.../java/org/apache/calcite/rex/RexSimplify.java | 148 ++++++++++++++-------
.../main/java/org/apache/calcite/rex/RexUtil.java | 5 +-
.../org/apache/calcite/rex/RexProgramTest.java | 48 ++++++-
core/src/test/resources/sql/sub-query.iq | 8 +-
5 files changed, 169 insertions(+), 60 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/rex/RexInterpreter.java
b/core/src/main/java/org/apache/calcite/rex/RexInterpreter.java
index 8a4012e..80adc5c 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexInterpreter.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexInterpreter.java
@@ -219,6 +219,8 @@ public class RexInterpreter implements
RexVisitor<Comparable> {
return extract(values);
case LIKE:
return like(values);
+ case SIMILAR:
+ return similar(values);
case SEARCH:
return search(call.operands.get(1).getType().getSqlTypeName(), values);
default:
@@ -261,6 +263,24 @@ public class RexInterpreter implements
RexVisitor<Comparable> {
}
}
+ private static Comparable similar(List<Comparable> values) {
+ if (containsNull(values)) {
+ return N;
+ }
+ final NlsString value = (NlsString) values.get(0);
+ final NlsString pattern = (NlsString) values.get(1);
+ switch (values.size()) {
+ case 2:
+ return SqlFunctions.similar(value.getValue(), pattern.getValue());
+ case 3:
+ final NlsString escape = (NlsString) values.get(2);
+ return SqlFunctions.similar(value.getValue(), pattern.getValue(),
+ escape.getValue());
+ default:
+ throw new AssertionError();
+ }
+ }
+
@SuppressWarnings({"BetaApi", "rawtypes", "unchecked", "UnstableApiUsage"})
private static Comparable search(SqlTypeName typeName, List<Comparable>
values) {
final Comparable value = values.get(0);
diff --git a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
index 0579025..14337e7 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
@@ -306,7 +306,7 @@ public class RexSimplify {
case SEARCH:
return simplifySearch((RexCall) e, unknownAs);
case LIKE:
- return simplifyLike((RexCall) e);
+ return simplifyLike((RexCall) e, unknownAs);
case MINUS_PREFIX:
return simplifyUnaryMinus((RexCall) e, unknownAs);
case PLUS_PREFIX:
@@ -320,6 +320,63 @@ public class RexSimplify {
}
}
+ /** Applies NOT to an expression. */
+ RexNode not(RexNode e) {
+ return RexUtil.not(rexBuilder, e);
+ }
+
+ /** Applies IS NOT FALSE to an expression. */
+ RexNode isNotFalse(RexNode e) {
+ return e.isAlwaysTrue()
+ ? rexBuilder.makeLiteral(true)
+ : e.isAlwaysFalse()
+ ? rexBuilder.makeLiteral(false)
+ : e.getKind() == SqlKind.NOT
+ ? isNotTrue(((RexCall) e).operands.get(0))
+ : predicates.isEffectivelyNotNull(e)
+ ? e // would "CAST(e AS BOOLEAN NOT NULL)" better?
+ : rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_FALSE, e);
+ }
+
+ /** Applies IS NOT TRUE to an expression. */
+ RexNode isNotTrue(RexNode e) {
+ return e.isAlwaysTrue()
+ ? rexBuilder.makeLiteral(false)
+ : e.isAlwaysFalse()
+ ? rexBuilder.makeLiteral(true)
+ : e.getKind() == SqlKind.NOT
+ ? isNotFalse(((RexCall) e).operands.get(0))
+ : predicates.isEffectivelyNotNull(e)
+ ? not(e)
+ : rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_TRUE, e);
+ }
+
+ /** Applies IS TRUE to an expression. */
+ RexNode isTrue(RexNode e) {
+ return e.isAlwaysTrue()
+ ? rexBuilder.makeLiteral(true)
+ : e.isAlwaysFalse()
+ ? rexBuilder.makeLiteral(false)
+ : e.getKind() == SqlKind.NOT
+ ? isFalse(((RexCall) e).operands.get(0))
+ : predicates.isEffectivelyNotNull(e)
+ ? e // would "CAST(e AS BOOLEAN NOT NULL)" better?
+ : rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE, e);
+ }
+
+ /** Applies IS FALSE to an expression. */
+ RexNode isFalse(RexNode e) {
+ return e.isAlwaysTrue()
+ ? rexBuilder.makeLiteral(false)
+ : e.isAlwaysFalse()
+ ? rexBuilder.makeLiteral(true)
+ : e.getKind() == SqlKind.NOT
+ ? isTrue(((RexCall) e).operands.get(0))
+ : predicates.isEffectivelyNotNull(e)
+ ? not(e)
+ : rexBuilder.makeCall(SqlStdOperatorTable.IS_FALSE, e);
+ }
+
/**
* Runs simplification inside a non-specialized node.
*/
@@ -332,16 +389,14 @@ public class RexSimplify {
return rexBuilder.makeCall(e.getType(), e.getOperator(), operands);
}
- private RexNode simplifyLike(RexCall e) {
+ private RexNode simplifyLike(RexCall e, RexUnknownAs unknownAs) {
if (e.operands.get(1) instanceof RexLiteral) {
final RexLiteral literal = (RexLiteral) e.operands.get(1);
if ("%".equals(literal.getValueAs(String.class))) {
- RexNode x = e.operands.get(0);
- // "x LIKE '%'" simplifies to "UNKNOWN OR x IS NOT NULL"
- return simplify(
- rexBuilder.makeCall(SqlStdOperatorTable.OR,
- rexBuilder.makeNullLiteral(e.getType()),
- rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, x)));
+ // "x LIKE '%'" simplifies to "x = x"
+ final RexNode x = e.operands.get(0);
+ return simplify(rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, x, x),
+ unknownAs);
}
}
return simplifyGenericNode(e);
@@ -397,7 +452,7 @@ public class RexSimplify {
return cmp.ref;
case LESS_THAN:
case NOT_EQUALS: // x!=true
- return simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT,
cmp.ref), unknownAs);
+ return simplify(not(cmp.ref), unknownAs);
case GREATER_THAN:
/* this is false, but could be null if x is null */
if (!cmp.ref.getType().isNullable()) {
@@ -418,7 +473,7 @@ public class RexSimplify {
switch (cmp.kind) {
case EQUALS:
case LESS_THAN_OR_EQUAL:
- return simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT,
cmp.ref), unknownAs);
+ return simplify(not(cmp.ref), unknownAs);
case NOT_EQUALS:
case GREATER_THAN:
return cmp.ref;
@@ -561,8 +616,7 @@ public class RexSimplify {
final RexNode t2 = simplify.simplify(t, unknownAs);
terms.set(i, t2);
final RexNode inverse =
-
simplify.simplify(rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_TRUE, t2),
- RexUnknownAs.UNKNOWN);
+ simplify.simplify(isNotTrue(t2), RexUnknownAs.UNKNOWN);
final RelOptPredicateList newPredicates =
simplify.predicates.union(rexBuilder,
RelOptPredicateList.of(rexBuilder, ImmutableList.of(inverse)));
simplify = simplify.withPredicates(newPredicates);
@@ -617,9 +671,7 @@ public class RexSimplify {
// NOT distributivity for AND
newOperands = new ArrayList<>();
for (RexNode operand : ((RexCall) a).getOperands()) {
- newOperands.add(
- simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, operand),
- unknownAs));
+ newOperands.add(simplify(not(operand), unknownAs));
}
return simplify(
rexBuilder.makeCall(SqlStdOperatorTable.OR, newOperands), unknownAs);
@@ -628,9 +680,7 @@ public class RexSimplify {
// NOT distributivity for OR
newOperands = new ArrayList<>();
for (RexNode operand : ((RexCall) a).getOperands()) {
- newOperands.add(
- simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, operand),
- unknownAs));
+ newOperands.add(simplify(not(operand), unknownAs));
}
return simplify(
rexBuilder.makeCall(SqlStdOperatorTable.AND, newOperands),
unknownAs);
@@ -640,10 +690,10 @@ public class RexSimplify {
List<RexNode> operands = ((RexCall) a).getOperands();
for (int i = 0; i < operands.size(); i += 2) {
if (i + 1 == operands.size()) {
- newOperands.add(rexBuilder.makeCall(SqlStdOperatorTable.NOT,
operands.get(i)));
+ newOperands.add(not(operands.get(i)));
} else {
newOperands.add(operands.get(i));
- newOperands.add(rexBuilder.makeCall(SqlStdOperatorTable.NOT,
operands.get(i + 1)));
+ newOperands.add(not(operands.get(i + 1)));
}
}
return simplify(
@@ -673,13 +723,7 @@ public class RexSimplify {
if (a == a2) {
return call;
}
- if (a2.isAlwaysTrue()) {
- return rexBuilder.makeLiteral(false);
- }
- if (a2.isAlwaysFalse()) {
- return rexBuilder.makeLiteral(true);
- }
- return rexBuilder.makeCall(SqlStdOperatorTable.NOT, a2);
+ return not(a2);
}
private RexNode simplifyUnaryMinus(RexCall call, RexUnknownAs unknownAs) {
@@ -714,13 +758,13 @@ public class RexSimplify {
return simplify(a, unknownAs);
}
if (kind == SqlKind.IS_FALSE && unknownAs == RexUnknownAs.FALSE) {
- return simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, a),
unknownAs);
+ return simplify(not(a), unknownAs);
}
if (kind == SqlKind.IS_NOT_FALSE && unknownAs == RexUnknownAs.TRUE) {
return simplify(a, unknownAs);
}
if (kind == SqlKind.IS_NOT_TRUE && unknownAs == RexUnknownAs.TRUE) {
- return simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, a),
unknownAs);
+ return simplify(not(a), unknownAs);
}
final RexNode pred = simplifyIsPredicate(kind, a);
if (pred != null) {
@@ -766,28 +810,38 @@ public class RexSimplify {
return simplified;
}
break;
+
case IS_TRUE:
- case IS_NOT_FALSE:
// x IS TRUE ==> x (if x is not nullable)
+ if (predicates.isEffectivelyNotNull(a)) {
+ return simplify(a, unknownAs);
+ }
+ simplified = simplify(a, RexUnknownAs.FALSE);
+ if (simplified == a) {
+ return null;
+ }
+ return isTrue(simplified);
+
+ case IS_NOT_FALSE:
// x IS NOT FALSE ==> x (if x is not nullable)
if (predicates.isEffectivelyNotNull(a)) {
return simplify(a, unknownAs);
- } else {
- RexNode newSub =
- simplify(a, kind == SqlKind.IS_TRUE ? RexUnknownAs.FALSE :
RexUnknownAs.TRUE);
- if (newSub == a) {
- return null;
- }
- return rexBuilder.makeCall(RexUtil.op(kind), ImmutableList.of(newSub));
}
+ simplified = simplify(a, RexUnknownAs.TRUE);
+ if (simplified == a) {
+ return null;
+ }
+ return isNotFalse(simplified);
+
case IS_FALSE:
case IS_NOT_TRUE:
// x IS NOT TRUE ==> NOT x (if x is not nullable)
// x IS FALSE ==> NOT x (if x is not nullable)
if (predicates.isEffectivelyNotNull(a)) {
- return simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, a),
unknownAs);
+ return simplify(not(a), unknownAs);
}
break;
+
default:
break;
}
@@ -1252,7 +1306,7 @@ public class RexSimplify {
return r.accept(SafeRexVisitor.INSTANCE);
}
- private static @Nullable RexNode simplifyBooleanCase(RexBuilder rexBuilder,
+ private @Nullable RexNode simplifyBooleanCase(RexBuilder rexBuilder,
List<CaseBranch> inputBranches, @SuppressWarnings("unused") RexUnknownAs
unknownAs,
RelDataType branchType) {
RexNode result;
@@ -1266,13 +1320,8 @@ public class RexSimplify {
|| !isSafeExpression(branch.value)) {
return null;
}
- RexNode cond;
- RexNode value;
- if (branch.cond.getType().isNullable()) {
- cond = rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE, branch.cond);
- } else {
- cond = branch.cond;
- }
+ final RexNode cond = isTrue(branch.cond);
+ final RexNode value;
if (!branchType.equals(branch.value.getType())) {
value = rexBuilder.makeAbstractCast(branchType, branch.value);
} else {
@@ -1419,10 +1468,7 @@ public class RexSimplify {
}
// Add the NOT disjunctions back in.
for (RexNode notDisjunction : notTerms) {
- terms.add(
- simplify(
- rexBuilder.makeCall(SqlStdOperatorTable.NOT, notDisjunction),
- UNKNOWN));
+ terms.add(simplify(not(notDisjunction), UNKNOWN));
}
return RexUtil.composeConjunction(rexBuilder, terms);
}
@@ -1652,7 +1698,7 @@ public class RexSimplify {
}
// Add the NOT disjunctions back in.
for (RexNode notDisjunction : notTerms) {
- terms.add(rexBuilder.makeCall(SqlStdOperatorTable.NOT, notDisjunction));
+ terms.add(not(notDisjunction));
}
// The negated terms: only deterministic expressions
for (RexNode negatedTerm : negatedTerms) {
diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java
b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
index 9d1ff99..cfe0a8f 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
@@ -2208,7 +2208,10 @@ public class RexUtil {
return e -> not(rexBuilder, e);
}
- /** Applies NOT to an expression. */
+ /** Applies NOT to an expression.
+ *
+ * <p>Unlike {@link #not}, may strengthen the type from {@code BOOLEAN}
+ * to {@code BOOLEAN NOT NULL}. */
static RexNode not(final RexBuilder rexBuilder, RexNode input) {
return input.isAlwaysTrue()
? rexBuilder.makeLiteral(false)
diff --git a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
index 1a8a748..8df9bea 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
@@ -2928,6 +2928,11 @@ class RexProgramTest extends RexProgramTestBase {
"?0.bool0");
}
+ @Test void testSimplifyIsTrue() {
+ final RexNode ref = input(tVarchar(true, 10), 0);
+ checkSimplify(isTrue(like(ref, literal("%"))), "IS NOT NULL($0)");
+ }
+
/** Unit tests for
* <a
href="https://issues.apache.org/jira/browse/CALCITE-2438">[CALCITE-2438]
* RexCall#isAlwaysTrue returns incorrect result</a>. */
@@ -3084,16 +3089,51 @@ class RexProgramTest extends RexProgramTestBase {
* 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))");
+ checkSimplify3(like(ref, literal("%")),
+ "OR(null, IS NOT NULL($0))", "IS NOT NULL($0)", "true");
+ checkSimplify3(like(ref, literal("%"), literal("#")),
+ "OR(null, IS NOT NULL($0))", "IS NOT NULL($0)", "true");
+ checkSimplify3(
+ or(like(ref, literal("%")),
+ like(ref, literal("% %"))),
+ "OR(null, IS NOT NULL($0), LIKE($0, '% %'))",
+ "OR(IS NOT NULL($0), LIKE($0, '% %'))", "true");
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("#")));
+
+ // As above, but ref is NOT NULL
+ final RexNode refMandatory = vVarcharNotNull(0);
+ checkSimplify(like(refMandatory, literal("%")), "true");
+ checkSimplify(
+ or(like(refMandatory, literal("%")),
+ like(refMandatory, literal("% %"))), "true");
+
+ // NOT LIKE and NOT SIMILAR TO are not allowed in Rex land
+ try {
+ rexBuilder.makeCall(SqlStdOperatorTable.NOT_LIKE, ref, literal("%"));
+ } catch (AssertionError e) {
+ assertThat(e.getMessage(), is("unsupported negated operator NOT LIKE"));
+ }
+ try {
+ rexBuilder.makeCall(SqlStdOperatorTable.NOT_SIMILAR_TO, ref,
literal("%"));
+ } catch (AssertionError e) {
+ assertThat(e.getMessage(),
+ is("unsupported negated operator NOT SIMILAR TO"));
+ }
+
+ // NOT(LIKE)
+ checkSimplify3(not(like(ref, literal("%"))),
+ "NOT(OR(null, IS NOT NULL($0)))", "false", "NOT(IS NOT NULL($0))");
+ // SIMILAR TO is not optimized
+ checkSimplifyUnchanged(
+ rexBuilder.makeCall(SqlStdOperatorTable.SIMILAR_TO, ref,
literal("%")));
+ // NOT(SIMILAR TO) is not optimized
+ checkSimplifyUnchanged(
+ not(rexBuilder.makeCall(SqlStdOperatorTable.SIMILAR_TO, ref,
literal("%"))));
}
@Test void testSimplifyNonDeterministicFunction() {
diff --git a/core/src/test/resources/sql/sub-query.iq
b/core/src/test/resources/sql/sub-query.iq
index a8844be..c158571 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -809,7 +809,7 @@ from "scott".emp;
(14 rows)
!ok
-EnumerableCalc(expr#0..3=[{inputs}], expr#4=[NOT($t2)], expr#5=[IS TRUE($t4)],
expr#6=[null:BOOLEAN], expr#7=[IS NOT NULL($t3)], expr#8=[AND($t5, $t6, $t7)],
expr#9=[IS NOT NULL($t2)], expr#10=[IS NOT FALSE($t2)], expr#11=[AND($t9, $t7,
$t10)], expr#12=[OR($t8, $t11)], SAL=[$t1], EXPR$1=[$t12])
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS FALSE($t2)],
expr#5=[null:BOOLEAN], expr#6=[IS NOT NULL($t3)], expr#7=[AND($t4, $t5, $t6)],
expr#8=[IS NOT NULL($t2)], expr#9=[IS NOT FALSE($t2)], expr#10=[AND($t8, $t6,
$t9)], expr#11=[OR($t7, $t10)], SAL=[$t1], EXPR$1=[$t11])
EnumerableNestedLoopJoin(condition=[true], joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
EnumerableTableScan(table=[[scott, EMP]])
@@ -987,7 +987,7 @@ from "scott".emp;
(14 rows)
!ok
-EnumerableCalc(expr#0..3=[{inputs}], expr#4=[NOT($t2)], expr#5=[IS TRUE($t4)],
expr#6=[null:BOOLEAN], expr#7=[IS NOT NULL($t3)], expr#8=[AND($t5, $t6, $t7)],
expr#9=[IS NOT NULL($t2)], expr#10=[IS NOT FALSE($t2)], expr#11=[AND($t9, $t7,
$t10)], expr#12=[OR($t8, $t11)], SAL=[$t1], EXPR$1=[$t12])
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS FALSE($t2)],
expr#5=[null:BOOLEAN], expr#6=[IS NOT NULL($t3)], expr#7=[AND($t4, $t5, $t6)],
expr#8=[IS NOT NULL($t2)], expr#9=[IS NOT FALSE($t2)], expr#10=[AND($t8, $t6,
$t9)], expr#11=[OR($t7, $t10)], SAL=[$t1], EXPR$1=[$t11])
EnumerableNestedLoopJoin(condition=[true], joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
EnumerableTableScan(table=[[scott, EMP]])
@@ -1059,7 +1059,7 @@ from "scott".emp;
(14 rows)
!ok
-EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[NOT($t2)],
expr#6=[IS TRUE($t5)], expr#7=[null:BOOLEAN], expr#8=[AND($t6, $t7)],
expr#9=[IS NOT FALSE($t2)], expr#10=[IS NULL($t2)], expr#11=[AND($t9, $t10)],
expr#12=[OR($t4, $t8, $t11)], SAL=[$t1], EXPR$1=[$t12])
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[IS
FALSE($t2)], expr#6=[null:BOOLEAN], expr#7=[AND($t5, $t6)], expr#8=[IS NOT
FALSE($t2)], expr#9=[IS NULL($t2)], expr#10=[AND($t8, $t9)], expr#11=[OR($t4,
$t7, $t10)], SAL=[$t1], EXPR$1=[$t11])
EnumerableNestedLoopJoin(condition=[true], joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
EnumerableTableScan(table=[[scott, EMP]])
@@ -1237,7 +1237,7 @@ from "scott".emp;
(14 rows)
!ok
-EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[NOT($t2)],
expr#6=[IS TRUE($t5)], expr#7=[null:BOOLEAN], expr#8=[AND($t6, $t7)],
expr#9=[IS NOT FALSE($t2)], expr#10=[IS NULL($t2)], expr#11=[AND($t9, $t10)],
expr#12=[OR($t4, $t8, $t11)], SAL=[$t1], EXPR$1=[$t12])
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[IS
FALSE($t2)], expr#6=[null:BOOLEAN], expr#7=[AND($t5, $t6)], expr#8=[IS NOT
FALSE($t2)], expr#9=[IS NULL($t2)], expr#10=[AND($t8, $t9)], expr#11=[OR($t4,
$t7, $t10)], SAL=[$t1], EXPR$1=[$t11])
EnumerableNestedLoopJoin(condition=[true], joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
EnumerableTableScan(table=[[scott, EMP]])