[CALCITE-4249] JDBC adapter cannot translate NOT LIKE in join condition

Fix bug translating Sarg to SQL, and add a test case for it.
(The test case only demonstrates the bug on [CALCITE-4262]
branch, where SqlToRelCovnerter uses RelBuilder.filter rather
than LogicalFilter.create, and therefore introduces a Sarg.)
diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
index f0756a8..3d26ed2 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
@@ -51,7 +51,6 @@
 import org.apache.calcite.sql.JoinType;
 import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlBasicCall;
-import org.apache.calcite.sql.SqlBinaryOperator;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDialect;
 import org.apache.calcite.sql.SqlDynamicParam;
@@ -66,6 +65,7 @@
 import org.apache.calcite.sql.SqlSelect;
 import org.apache.calcite.sql.SqlSelectKeyword;
 import org.apache.calcite.sql.SqlSetOperator;
+import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.SqlWindow;
 import org.apache.calcite.sql.fun.SqlCase;
 import org.apache.calcite.sql.fun.SqlCountAggFunction;
@@ -129,6 +129,21 @@
 
   protected final Map<CorrelationId, Context> correlTableMap = new HashMap<>();
 
+  /** Private RexBuilder for short-lived expressions. It has its own
+   * dedicated type factory, so don't trust the types to be canonized. */
+  final RexBuilder rexBuilder =
+      new RexBuilder(new SqlTypeFactoryImpl(RelDataTypeSystemImpl.DEFAULT));
+
+  /** Maps a {@link SqlKind} to a {@link SqlOperator} that implements NOT
+   * applied to that kind. */
+  private static final Map<SqlKind, SqlOperator> NOT_KIND_OPERATORS =
+      ImmutableMap.<SqlKind, SqlOperator>builder()
+          .put(SqlKind.IN, SqlStdOperatorTable.NOT_IN)
+          .put(SqlKind.NOT_IN, SqlStdOperatorTable.IN)
+          .put(SqlKind.LIKE, SqlStdOperatorTable.NOT_LIKE)
+          .put(SqlKind.SIMILAR, SqlStdOperatorTable.NOT_SIMILAR_TO)
+          .build();
+
   protected SqlImplementor(SqlDialect dialect) {
     this.dialect = Objects.requireNonNull(dialect);
   }
@@ -284,21 +299,27 @@
     final SqlOperator op;
     final Context joinContext;
     switch (node.getKind()) {
+    case NOT:
+      final RexNode operand0 = ((RexCall) node).getOperands().get(0);
+      final SqlOperator notOperator = NOT_KIND_OPERATORS.get(operand0.getKind());
+      if (notOperator != null) {
+        return convertConditionToSqlNode(
+            leftContext.implementor().rexBuilder.makeCall(notOperator,
+                ((RexCall) operand0).operands), leftContext, rightContext,
+            leftFieldCount, dialect);
+      }
+      // fall through
     case AND:
     case OR:
       operands = ((RexCall) node).getOperands();
       op = ((RexCall) node).getOperator();
-      SqlNode sqlCondition = null;
+      final List<SqlNode> sqlOperands = new ArrayList<>();
       for (RexNode operand : operands) {
-        SqlNode x = convertConditionToSqlNode(operand, leftContext,
-            rightContext, leftFieldCount, dialect);
-        if (sqlCondition == null) {
-          sqlCondition = x;
-        } else {
-          sqlCondition = op.createCall(POS, sqlCondition, x);
-        }
+        sqlOperands.add(
+            convertConditionToSqlNode(operand, leftContext,
+                rightContext, leftFieldCount, dialect));
       }
-      return sqlCondition;
+      return SqlUtil.createCall(op, POS, sqlOperands);
 
     case EQUALS:
     case IS_DISTINCT_FROM:
@@ -774,19 +795,16 @@
       case NOT:
         RexNode operand = ((RexCall) rex).operands.get(0);
         final SqlNode node = toSql(program, operand);
-        switch (operand.getKind()) {
-        case IN:
-          assert operand instanceof RexSubQuery
-              : "scalar IN is no longer allowed in RexCall: " + rex;
-          return SqlStdOperatorTable.NOT_IN
-              .createCall(POS, ((SqlCall) node).getOperandList());
-        case LIKE:
-          return SqlStdOperatorTable.NOT_LIKE
-              .createCall(POS, ((SqlCall) node).getOperandList());
-        case SIMILAR:
-          return SqlStdOperatorTable.NOT_SIMILAR_TO
-              .createCall(POS, ((SqlCall) node).getOperandList());
-        default:
+        final SqlOperator inverseOperator = NOT_KIND_OPERATORS.get(operand.getKind());
+        if (inverseOperator != null) {
+          switch (operand.getKind()) {
+          case IN:
+            assert operand instanceof RexSubQuery
+                : "scalar IN is no longer allowed in RexCall: " + rex;
+          }
+          return inverseOperator.createCall(POS,
+              ((SqlCall) node).getOperandList());
+        } else {
           return SqlStdOperatorTable.NOT.createCall(POS, node);
         }
 
@@ -821,17 +839,7 @@
             nodeList.add(dialect.getCastSpec(call.getType()));
           }
         }
-        if (op instanceof SqlBinaryOperator && nodeList.size() > 2) {
-          // In RexNode trees, OR and AND have any number of children;
-          // SqlCall requires exactly 2. So, convert to a balanced binary
-          // tree for OR/AND, left-deep binary tree for others.
-          if (op.kind == SqlKind.OR || op.kind == SqlKind.AND) {
-            return createBalancedCall(op, nodeList, 0, nodeList.size());
-          } else {
-            return createLeftCall(op, nodeList);
-          }
-        }
-        return op.createCall(new SqlNodeList(nodeList, POS));
+        return SqlUtil.createCall(op, POS, nodeList);
       }
     }
 
@@ -841,9 +849,6 @@
     private <C extends Comparable<C>> SqlNode toSql(RexProgram program,
         RexNode operand, RelDataType type, Sarg<C> sarg) {
       final List<SqlNode> orList = new ArrayList<>();
-      final RexBuilder rexBuilder =
-          new RexBuilder(
-              new SqlTypeFactoryImpl(RelDataTypeSystemImpl.DEFAULT));
       final SqlNode operandSql = toSql(program, operand);
       if (sarg.containsNull) {
         orList.add(SqlStdOperatorTable.IS_NULL.createCall(POS, operandSql));
@@ -852,8 +857,8 @@
         final SqlNodeList list = sarg.rangeSet.asRanges().stream()
             .map(range ->
                 toSql(program,
-                    rexBuilder.makeLiteral(range.lowerEndpoint(), type, true,
-                        true)))
+                    implementor().rexBuilder.makeLiteral(range.lowerEndpoint(),
+                        type, true, true)))
             .collect(SqlNode.toList());
         switch (list.size()) {
         case 1:
@@ -867,17 +872,11 @@
       } else {
         final RangeSets.Consumer<C> consumer =
             new RangeToSql<>(operandSql, orList, v ->
-                toSql(program, rexBuilder.makeLiteral(v, type, false)));
+                toSql(program,
+                    implementor().rexBuilder.makeLiteral(v, type, false)));
         RangeSets.forEach(sarg.rangeSet, consumer);
       }
-      switch (orList.size()) {
-      case 0:
-        return SqlLiteral.createBoolean(false, POS);
-      case 1:
-        return orList.get(0);
-      default:
-        return SqlStdOperatorTable.OR.createCall(POS, orList);
-      }
+      return SqlUtil.createCall(SqlStdOperatorTable.OR, POS, orList);
     }
 
     /** Converts an expression from {@link RexWindowBound} to {@link SqlNode}
@@ -1058,30 +1057,6 @@
           + rexWindowBound);
     }
 
-    private SqlNode createLeftCall(SqlOperator op, List<SqlNode> nodeList) {
-      SqlNode node = op.createCall(new SqlNodeList(nodeList.subList(0, 2), POS));
-      for (int i = 2; i < nodeList.size(); i++) {
-        node = op.createCall(new SqlNodeList(ImmutableList.of(node, nodeList.get(i)), POS));
-      }
-      return node;
-    }
-
-    /**
-     * Create a balanced binary call from sql node list,
-     * start inclusive, end exclusive.
-     */
-    private SqlNode createBalancedCall(SqlOperator op,
-        List<SqlNode> nodeList, int start, int end) {
-      assert start < end && end <= nodeList.size();
-      if (start + 1 == end) {
-        return nodeList.get(start);
-      }
-      int mid = (end - start) / 2 + start;
-      SqlNode leftNode = createBalancedCall(op, nodeList, start, mid);
-      SqlNode rightNode = createBalancedCall(op, nodeList, mid, end);
-      return op.createCall(new SqlNodeList(ImmutableList.of(leftNode, rightNode), POS));
-    }
-
     private List<SqlNode> toSql(RexProgram program, List<RexNode> operandList) {
       final List<SqlNode> list = new ArrayList<>();
       for (RexNode rex : operandList) {
@@ -1230,7 +1205,9 @@
       }
 
       private void addAnd(SqlNode... nodes) {
-        list.add(SqlStdOperatorTable.AND.createCall(POS, nodes));
+        list.add(
+            SqlUtil.createCall(SqlStdOperatorTable.AND, POS,
+                ImmutableList.copyOf(nodes)));
       }
 
       private SqlNode op(SqlOperator op, C value) {
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
index 13144d4..b68f085 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
@@ -1098,6 +1098,63 @@
     return (RelNode) rel;
   }
 
+  /** Creates a call to an operator.
+   *
+   * <p>Deals with the fact the AND and OR are binary. */
+  public static SqlNode createCall(SqlOperator op, SqlParserPos pos,
+      List<SqlNode> operands) {
+    switch (op.kind) {
+    case OR:
+    case AND:
+      // In RexNode trees, OR and AND have any number of children;
+      // SqlCall requires exactly 2. So, convert to a balanced binary
+      // tree for OR/AND, left-deep binary tree for others.
+      switch (operands.size()) {
+      case 0:
+        return SqlLiteral.createBoolean(op.kind == SqlKind.AND, pos);
+      case 1:
+        return operands.get(0);
+      default:
+        return createBalancedCall(op, pos, operands, 0, operands.size());
+      case 2:
+      case 3:
+      case 4:
+      case 5:
+        // fall through
+      }
+      // fall through
+    }
+    if (op instanceof SqlBinaryOperator && operands.size() > 2) {
+      return createLeftCall(op, pos, operands);
+    }
+    return op.createCall(pos, operands);
+  }
+
+  private static SqlNode createLeftCall(SqlOperator op, SqlParserPos pos,
+      List<SqlNode> nodeList) {
+    SqlNode node = op.createCall(pos, nodeList.subList(0, 2));
+    for (int i = 2; i < nodeList.size(); i++) {
+      node = op.createCall(pos, node, nodeList.get(i));
+    }
+    return node;
+  }
+
+  /**
+   * Creates a balanced binary call from sql node list,
+   * start inclusive, end exclusive.
+   */
+  private static SqlNode createBalancedCall(SqlOperator op, SqlParserPos pos,
+      List<SqlNode> operands, int start, int end) {
+    assert start < end && end <= operands.size();
+    if (start + 1 == end) {
+      return operands.get(start);
+    }
+    int mid = (end - start) / 2 + start;
+    SqlNode leftNode = createBalancedCall(op, pos, operands, start, mid);
+    SqlNode rightNode = createBalancedCall(op, pos, operands, mid, end);
+    return op.createCall(pos, leftNode, rightNode);
+  }
+
   //~ Inner Classes ----------------------------------------------------------
 
   /**
diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index c8a6065..e3b4350 100644
--- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -219,6 +219,16 @@
     sql(query).ok(expected);
   }
 
+  @Test void testSelectWhereNotEqualsOrNull() {
+    String query = "select \"product_id\", \"shelf_width\"\n"
+        + "from \"product\"\n"
+        + "where \"net_weight\" <> 10 or \"net_weight\" is null";
+    final String expected = "SELECT \"product_id\", \"shelf_width\"\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "WHERE \"net_weight\" <> 10 OR \"net_weight\" IS NULL";
+    sql(query).ok(expected);
+  }
+
   @Test void testSelectQueryWithWhereClauseOfBasicOperators() {
     String query = "select * from \"product\" "
         + "where (\"product_id\" = 10 OR \"product_id\" <= 5) "
@@ -2422,6 +2432,30 @@
     assertThat(s, notNullValue()); // sufficient that conversion did not throw
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-4249">[CALCITE-4249]
+   * JDBC adapter cannot translate NOT LIKE in join condition</a>. */
+  @Test void testJoinOnNotLike() {
+    final Function<RelBuilder, RelNode> relFn = b ->
+        b.scan("EMP")
+            .scan("DEPT")
+            .join(JoinRelType.LEFT,
+                b.and(
+                    b.equals(b.field(2, 0, "DEPTNO"),
+                        b.field(2, 1, "DEPTNO")),
+                    b.not(
+                        b.call(SqlStdOperatorTable.LIKE,
+                            b.field(2, 1, "DNAME"),
+                            b.literal("ACCOUNTING")))))
+            .build();
+    final String expectedSql = "SELECT *\n"
+        + "FROM \"scott\".\"EMP\"\n"
+        + "LEFT JOIN \"scott\".\"DEPT\" "
+        + "ON \"EMP\".\"DEPTNO\" = \"DEPT\".\"DEPTNO\" "
+        + "AND \"DEPT\".\"DNAME\" NOT LIKE 'ACCOUNTING'";
+    relFn(relFn).ok(expectedSql);
+  }
+
   @Test void testCartesianProductWithInnerJoinSyntax() {
     String query = "select * from \"department\"\n"
         + "INNER JOIN \"employee\" ON TRUE";