You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2017/04/26 22:15:27 UTC

[3/4] calcite git commit: Fix OVERLAPS operator

Fix OVERLAPS operator

Refactor StandardConvertletTable, creating methods to help create
calls to common operators.


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/c94a080b
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/c94a080b
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/c94a080b

Branch: refs/heads/master
Commit: c94a080b3520a568e5452cc33ca3a443cf2f50ab
Parents: 39c22f0
Author: Julian Hyde <jh...@apache.org>
Authored: Wed Apr 19 14:46:05 2017 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Wed Apr 26 14:21:11 2017 -0700

----------------------------------------------------------------------
 .../sql2rel/StandardConvertletTable.java        | 215 ++++++++-----------
 .../main/java/org/apache/calcite/util/Bug.java  |   7 -
 .../calcite/sql/test/SqlOperatorBaseTest.java   | 134 ++++++++----
 3 files changed, 188 insertions(+), 168 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/c94a080b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index 5523c81..90f1ffc 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -361,6 +361,51 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
 
   //~ Methods ----------------------------------------------------------------
 
+  private RexNode or(RexBuilder rexBuilder, RexNode a0, RexNode a1) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.OR, a0, a1);
+  }
+
+  private RexNode ge(RexBuilder rexBuilder, RexNode a0, RexNode a1) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, a0,
+        a1);
+  }
+
+  private RexNode le(RexBuilder rexBuilder, RexNode a0, RexNode a1) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, a0, a1);
+  }
+
+  private RexNode and(RexBuilder rexBuilder, RexNode a0, RexNode a1) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.AND, a0, a1);
+  }
+
+  private static RexNode divideInt(RexBuilder rexBuilder, RexNode a0,
+      RexNode a1) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.DIVIDE_INTEGER, a0, a1);
+  }
+
+  private RexNode plus(RexBuilder rexBuilder, RexNode a0, RexNode a1) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.PLUS, a0, a1);
+  }
+
+  private RexNode minus(RexBuilder rexBuilder, RexNode a0, RexNode a1) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.MINUS, a0, a1);
+  }
+
+  private static RexNode multiply(RexBuilder rexBuilder, RexNode a0,
+      RexNode a1) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.MULTIPLY, a0, a1);
+  }
+
+  private RexNode case_(RexBuilder rexBuilder, RexNode... args) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.CASE, args);
+  }
+
+  // SqlNode helpers
+
+  private SqlCall plus(SqlParserPos pos, SqlNode a0, SqlNode a1) {
+    return SqlStdOperatorTable.PLUS.createCall(pos, a0, a1);
+  }
+
   /**
    * Converts a CASE expression.
    */
@@ -545,45 +590,25 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
           interval.getIntervalQualifier().getStartUnit().multiplier;
       RexNode rexInterval = cx.convertExpression(literal);
 
-      RexNode res;
-
       final RexBuilder rexBuilder = cx.getRexBuilder();
       RexNode zero = rexBuilder.makeExactLiteral(BigDecimal.valueOf(0));
-      RexNode cond =
-          rexBuilder.makeCall(
-              SqlStdOperatorTable.GREATER_THAN_OR_EQUAL,
-              rexInterval,
-              zero);
+      RexNode cond = ge(rexBuilder, rexInterval, zero);
 
       RexNode pad =
           rexBuilder.makeExactLiteral(val.subtract(BigDecimal.ONE));
       RexNode cast = rexBuilder.makeReinterpretCast(
           rexInterval.getType(), pad, rexBuilder.makeLiteral(false));
-      SqlOperator op =
-          floor ? SqlStdOperatorTable.MINUS
-              : SqlStdOperatorTable.PLUS;
-      RexNode sum = rexBuilder.makeCall(op, rexInterval, cast);
-
-      RexNode kase =
-          floor
-          ? rexBuilder.makeCall(SqlStdOperatorTable.CASE,
-              cond, rexInterval, sum)
-          : rexBuilder.makeCall(SqlStdOperatorTable.CASE,
-              cond, sum, rexInterval);
+      RexNode sum = floor
+          ? minus(rexBuilder, rexInterval, cast)
+          : plus(rexBuilder, rexInterval, cast);
+
+      RexNode kase = floor
+          ? case_(rexBuilder, rexInterval, cond, sum)
+          : case_(rexBuilder, sum, cond, rexInterval);
 
       RexNode factor = rexBuilder.makeExactLiteral(val);
-      RexNode div =
-          rexBuilder.makeCall(
-              SqlStdOperatorTable.DIVIDE_INTEGER,
-              kase,
-              factor);
-      RexNode mult =
-          rexBuilder.makeCall(
-              SqlStdOperatorTable.MULTIPLY,
-              div,
-              factor);
-      res = mult;
-      return res;
+      RexNode div = divideInt(rexBuilder, kase, factor);
+      return multiply(rexBuilder, div, factor);
     }
 
     // normal floor, ceil function
@@ -667,10 +692,10 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
       case DATE:
         res = rexBuilder.makeCall(resType, SqlStdOperatorTable.EXTRACT_DATE,
             ImmutableList.of(rexBuilder.makeFlag(TimeUnitRange.MONTH), res));
-        res = rexBuilder.makeCall(SqlStdOperatorTable.MINUS, res,
-            rexBuilder.makeExactLiteral(BigDecimal.ONE));
+        res =
+            minus(rexBuilder, res, rexBuilder.makeExactLiteral(BigDecimal.ONE));
         res = divide(rexBuilder, res, unit.multiplier);
-        return rexBuilder.makeCall(SqlStdOperatorTable.PLUS, res,
+        return plus(rexBuilder, res,
             rexBuilder.makeExactLiteral(BigDecimal.ONE));
       }
       break;
@@ -731,13 +756,11 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
 
     res = mod(rexBuilder, resType, res, getFactor(unit));
     if (unit == TimeUnit.QUARTER) {
-      res = rexBuilder.makeCall(SqlStdOperatorTable.MINUS, res,
-          rexBuilder.makeExactLiteral(BigDecimal.ONE));
+      res = minus(rexBuilder, res, rexBuilder.makeExactLiteral(BigDecimal.ONE));
     }
     res = divide(rexBuilder, res, unit.multiplier);
     if (unit == TimeUnit.QUARTER) {
-      res = rexBuilder.makeCall(SqlStdOperatorTable.PLUS, res,
-          rexBuilder.makeExactLiteral(BigDecimal.ONE));
+      res = plus(rexBuilder, res, rexBuilder.makeExactLiteral(BigDecimal.ONE));
     }
     return res;
   }
@@ -787,14 +810,13 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
       try {
         final BigDecimal reciprocal =
             BigDecimal.ONE.divide(val, RoundingMode.UNNECESSARY);
-        return rexBuilder.makeCall(SqlStdOperatorTable.MULTIPLY, res,
+        return multiply(rexBuilder, res,
             rexBuilder.makeExactLiteral(reciprocal));
       } catch (ArithmeticException e) {
         // ignore - reciprocal is not an integer
       }
     }
-    return rexBuilder.makeCall(SqlStdOperatorTable.DIVIDE_INTEGER, res,
-        rexBuilder.makeExactLiteral(val));
+    return divideInt(rexBuilder, res, rexBuilder.makeExactLiteral(val));
   }
 
   public RexNode convertDatetimeMinus(
@@ -1091,19 +1113,9 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
     final RexNode z = list.get(SqlBetweenOperator.UPPER_OPERAND);
 
     final RexBuilder rexBuilder = cx.getRexBuilder();
-    RexNode ge1 =
-        rexBuilder.makeCall(
-            SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, x, y);
-    RexNode le1 =
-        rexBuilder.makeCall(
-            SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
-            x,
-            z);
-    RexNode and1 =
-        rexBuilder.makeCall(
-            SqlStdOperatorTable.AND,
-            ge1,
-            le1);
+    RexNode ge1 = ge(rexBuilder, x, y);
+    RexNode le1 = le(rexBuilder, x, z);
+    RexNode and1 = and(rexBuilder, ge1, le1);
 
     RexNode res;
     final SqlBetweenOperator.Flag symmetric = op.flag;
@@ -1112,26 +1124,10 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
       res = and1;
       break;
     case SYMMETRIC:
-      RexNode ge2 =
-          rexBuilder.makeCall(
-              SqlStdOperatorTable.GREATER_THAN_OR_EQUAL,
-              x,
-              z);
-      RexNode le2 =
-          rexBuilder.makeCall(
-              SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
-              x,
-              y);
-      RexNode and2 =
-          rexBuilder.makeCall(
-              SqlStdOperatorTable.AND,
-              ge2,
-              le2);
-      res =
-          rexBuilder.makeCall(
-              SqlStdOperatorTable.OR,
-              and1,
-              and2);
+      RexNode ge2 = ge(rexBuilder, x, z);
+      RexNode le2 = le(rexBuilder, x, y);
+      RexNode and2 = and(rexBuilder, ge2, le2);
+      res = or(rexBuilder, and1, and2);
       break;
     default:
       throw Util.unexpected(symmetric);
@@ -1198,60 +1194,33 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
     // intervals overlaps by: ~(t1 < t2 or t3 < t0)
     final SqlNode[] operands = ((SqlBasicCall) call).getOperands();
     assert operands.length == 4;
-    if (operands[1] instanceof SqlIntervalLiteral) {
+    final SqlParserPos pos = call.getParserPosition();
+    final RelDataType t1 = cx.getValidator().getValidatedNodeType(operands[1]);
+    if (SqlTypeUtil.isInterval(t1)) {
       // make t1 = t0 + t1 when t1 is an interval.
-      SqlOperator op1 = SqlStdOperatorTable.PLUS;
-      SqlNode[] second = new SqlNode[2];
-      second[0] = operands[0];
-      second[1] = operands[1];
-      operands[1] =
-          op1.createCall(
-              call.getParserPosition(),
-              second);
+      operands[1] = plus(pos, operands[0], operands[1]);
     }
-    if (operands[3] instanceof SqlIntervalLiteral) {
+    final RelDataType t3 = cx.getValidator().getValidatedNodeType(operands[3]);
+    if (SqlTypeUtil.isInterval(t3)) {
       // make t3 = t2 + t3 when t3 is an interval.
-      SqlOperator op1 = SqlStdOperatorTable.PLUS;
-      SqlNode[] four = new SqlNode[2];
-      four[0] = operands[2];
-      four[1] = operands[3];
-      operands[3] =
-          op1.createCall(
-              call.getParserPosition(),
-              four);
+      operands[3] = plus(pos, operands[2], operands[3]);
     }
 
-    // This captures t1 >= t2
-    SqlOperator op1 = SqlStdOperatorTable.GREATER_THAN_OR_EQUAL;
-    SqlNode[] left = new SqlNode[2];
-    left[0] = operands[1];
-    left[1] = operands[2];
-    SqlCall call1 =
-        op1.createCall(
-            call.getParserPosition(),
-            left);
-
-    // This captures t3 >= t0
-    SqlOperator op2 = SqlStdOperatorTable.GREATER_THAN_OR_EQUAL;
-    SqlNode[] right = new SqlNode[2];
-    right[0] = operands[3];
-    right[1] = operands[0];
-    SqlCall call2 =
-        op2.createCall(
-            call.getParserPosition(),
-            right);
-
-    // This captures t1 >= t2 and t3 >= t0
-    SqlOperator and = SqlStdOperatorTable.AND;
-    SqlNode[] overlaps = new SqlNode[2];
-    overlaps[0] = call1;
-    overlaps[1] = call2;
-    SqlCall call3 =
-        and.createCall(
-            call.getParserPosition(),
-            overlaps);
-
-    return cx.convertExpression(call3);
+    final RexNode r0 = cx.convertExpression(operands[0]);
+    final RexNode r1 = cx.convertExpression(operands[1]);
+    final RexNode r2 = cx.convertExpression(operands[2]);
+    final RexNode r3 = cx.convertExpression(operands[3]);
+
+    // Sort end points into start and end, such that (s0 <= e0) and (s1 <= e1).
+    final RexBuilder rexBuilder = cx.getRexBuilder();
+    final RexNode s0 = case_(rexBuilder, le(rexBuilder, r0, r1), r0, r1);
+    final RexNode e0 = case_(rexBuilder, le(rexBuilder, r0, r1), r1, r0);
+    final RexNode s1 = case_(rexBuilder, le(rexBuilder, r2, r3), r2, r3);
+    final RexNode e1 = case_(rexBuilder, le(rexBuilder, r2, r3), r3, r2);
+    // (e0 >= s1) AND (e1 >= s0)
+    return and(rexBuilder,
+        ge(rexBuilder, e0, s1),
+        ge(rexBuilder, e1, s0));
   }
 
   /**
@@ -1484,7 +1453,7 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
       final TimeUnit unit = unitLiteral.symbolValue(TimeUnit.class);
       return rexBuilder.makeCall(SqlStdOperatorTable.DATETIME_PLUS,
           cx.convertExpression(call.operand(2)),
-          rexBuilder.makeCall(SqlStdOperatorTable.MULTIPLY,
+          multiply(rexBuilder,
               rexBuilder.makeIntervalLiteral(unit.multiplier,
                   new SqlIntervalQualifier(unit, null,
                       unitLiteral.getParserPosition())),

http://git-wip-us.apache.org/repos/asf/calcite/blob/c94a080b/core/src/main/java/org/apache/calcite/util/Bug.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Bug.java b/core/src/main/java/org/apache/calcite/util/Bug.java
index 1adc2fe..e5d7096 100644
--- a/core/src/main/java/org/apache/calcite/util/Bug.java
+++ b/core/src/main/java/org/apache/calcite/util/Bug.java
@@ -108,13 +108,6 @@ public abstract class Bug {
   public static final boolean FRG78_FIXED = false;
 
   /**
-   * Whether <a href="http://issues.eigenbase.org/browse/FRG-187">issue
-   * FRG-187: FarragoAutoVmOperatorTest.testOverlapsOperator fails</a> is
-   * fixed.
-   */
-  public static final boolean FRG187_FIXED = false;
-
-  /**
    * Whether <a href="http://issues.eigenbase.org/browse/FRG-189">issue
    * FRG-189: FarragoAutoVmOperatorTest.testSelect fails</a> is fixed.
    */

http://git-wip-us.apache.org/repos/asf/calcite/blob/c94a080b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
index 2ba92ac..ff3c0e2 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
@@ -2313,46 +2313,80 @@ public abstract class SqlOperatorBaseTest {
 
   @Test public void testOverlapsOperator() {
     tester.setFor(SqlStdOperatorTable.OVERLAPS, VM_EXPAND);
-    if (Bug.FRG187_FIXED) {
-      tester.checkBoolean(
-          "(date '1-2-3', date '1-2-3') overlaps (date '1-2-3', interval '1' year)",
-          Boolean.TRUE);
-      tester.checkBoolean(
-          "(date '1-2-3', date '1-2-3') overlaps (date '4-5-6', interval '1' year)",
-          Boolean.FALSE);
-      tester.checkBoolean(
-          "(date '1-2-3', date '4-5-6') overlaps (date '2-2-3', date '3-4-5')",
-          Boolean.TRUE);
-      tester.checkNull(
-          "(cast(null as date), date '1-2-3') overlaps (date '1-2-3', interval '1' year)");
-      tester.checkNull(
-          "(date '1-2-3', date '1-2-3') overlaps (date '1-2-3', cast(null as date))");
+    tester.checkBoolean(
+        "(date '1-2-3', date '1-2-3') overlaps (date '1-2-3', interval '1' year)",
+        Boolean.TRUE);
+    tester.checkBoolean(
+        "(date '1-2-3', date '1-2-3') overlaps (date '4-5-6', interval '1' year)",
+        Boolean.FALSE);
+    tester.checkBoolean(
+        "(date '1-2-3', date '4-5-6') overlaps (date '2-2-3', date '3-4-5')",
+        Boolean.TRUE);
+    tester.checkNull(
+        "(cast(null as date), date '1-2-3') overlaps (date '1-2-3', interval '1' year)");
+    tester.checkNull(
+        "(date '1-2-3', date '1-2-3') overlaps (date '1-2-3', cast(null as date))");
 
-      tester.checkBoolean(
-          "(time '1:2:3', interval '1' second) overlaps (time '23:59:59', time '1:2:3')",
-          Boolean.TRUE);
-      tester.checkBoolean(
-          "(time '1:2:3', interval '1' second) overlaps (time '23:59:59', time '1:2:2')",
-          Boolean.FALSE);
-      tester.checkBoolean(
-          "(time '1:2:3', interval '1' second) overlaps (time '23:59:59', interval '2' hour)",
-          Boolean.TRUE);
-      tester.checkNull(
-          "(time '1:2:3', cast(null as time)) overlaps (time '23:59:59', time '1:2:3')");
-      tester.checkNull(
-          "(time '1:2:3', interval '1' second) overlaps (time '23:59:59', cast(null as interval hour))");
+    tester.checkBoolean(
+        "(time '1:2:3', interval '1' second) overlaps (time '23:59:59', time '1:2:3')",
+        Boolean.TRUE);
+    tester.checkBoolean(
+        "(time '1:2:3', interval '1' second) overlaps (time '23:59:59', time '1:2:2')",
+        Boolean.TRUE);
+    tester.checkBoolean(
+        "(time '1:2:3', interval '1' second) overlaps (time '23:59:59', interval '2' hour)",
+        Boolean.FALSE);
+    tester.checkNull(
+        "(time '1:2:3', cast(null as time)) overlaps (time '23:59:59', time '1:2:3')");
+    tester.checkNull(
+        "(time '1:2:3', interval '1' second) overlaps (time '23:59:59', cast(null as interval hour))");
 
-      tester.checkBoolean(
-          "(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' ) overlaps (timestamp '1-2-3 4:5:6', interval '1 2:3:4.5' day to second)",
-          Boolean.TRUE);
-      tester.checkBoolean(
-          "(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' ) overlaps (timestamp '2-2-3 4:5:6', interval '1 2:3:4.5' day to second)",
-          Boolean.FALSE);
-      tester.checkNull(
-          "(timestamp '1-2-3 4:5:6', cast(null as interval day) ) overlaps (timestamp '1-2-3 4:5:6', interval '1 2:3:4.5' day to second)");
-      tester.checkNull(
-          "(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' ) overlaps (cast(null as timestamp), interval '1 2:3:4.5' day to second)");
-    }
+    tester.checkBoolean(
+        "(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' ) overlaps (timestamp '1-2-3 4:5:6', interval '1 2:3:4.5' day to second)",
+        Boolean.TRUE);
+    tester.checkBoolean(
+        "(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' ) overlaps (timestamp '2-2-3 4:5:6', interval '1 2:3:4.5' day to second)",
+        Boolean.FALSE);
+    tester.checkNull(
+        "(timestamp '1-2-3 4:5:6', cast(null as interval day) ) overlaps (timestamp '1-2-3 4:5:6', interval '1 2:3:4.5' day to second)");
+    tester.checkNull(
+        "(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' ) overlaps (cast(null as timestamp), interval '1 2:3:4.5' day to second)");
+  }
+
+  @Test public void testOverlapsEtc() {
+    String[] times = {
+      "TIME '01:00:00'",
+      "TIME '02:00:00'",
+      "TIME '03:00:00'",
+      "TIME '04:00:00'",
+    };
+    String[] dates = {
+      "DATE '1970-01-01'",
+      "DATE '1970-02-01'",
+      "DATE '1970-03-01'",
+      "DATE '1970-04-01'",
+    };
+    String[] timestamps = {
+      "TIMESTAMP '1970-01-01 00:00:00'",
+      "TIMESTAMP '1970-02-01 00:00:00'",
+      "TIMESTAMP '1970-03-01 00:00:00'",
+      "TIMESTAMP '1970-04-01 00:00:00'",
+    };
+    checkOverlaps(new OverlapChecker(times));
+    checkOverlaps(new OverlapChecker(dates));
+    checkOverlaps(new OverlapChecker(timestamps));
+  }
+
+  private void checkOverlaps(OverlapChecker c) {
+    c.isFalse("($0,$1) OVERLAPS ($2,$3)");
+    c.isTrue("($0,$1) OVERLAPS ($1,$2)");
+    c.isTrue("($0,$2) OVERLAPS ($1,$3)");
+    c.isTrue("($0,$2) OVERLAPS ($3,$1)");
+    c.isTrue("($2,$0) OVERLAPS ($3,$1)");
+    c.isFalse("($3,$2) OVERLAPS ($1,$0)");
+    c.isTrue("($2,$3) OVERLAPS ($0,$2)");
+    c.isTrue("($2,$3) OVERLAPS ($2,$0)");
+    c.isTrue("($3,$2) OVERLAPS ($2,$0)");
   }
 
   @Test public void testLessThanOperator() {
@@ -6743,6 +6777,30 @@ public abstract class SqlOperatorBaseTest {
       this.values.add(new ValueType(type, null));
     }
   }
+
+  /** Runs an OVERLAPS test with a given set of literal values. */
+  class OverlapChecker {
+    final String[] values;
+
+    OverlapChecker(String... values) {
+      this.values = values;
+    }
+
+    public void isTrue(String s) {
+      tester.checkBoolean(sub(s), Boolean.TRUE);
+    }
+
+    public void isFalse(String s) {
+      tester.checkBoolean(sub(s), Boolean.FALSE);
+    }
+
+    private String sub(String s) {
+      return s.replace("$0", values[0])
+          .replace("$1", values[1])
+          .replace("$2", values[2])
+          .replace("$3", values[3]);
+    }
+  }
 }
 
 // End SqlOperatorBaseTest.java