@Override
  public SelType binaryOps(SelOp op, SelType rhs) {
    if (rhs.type() == SelTypes.NULL && (op == SelOp.EQUAL || op == SelOp.NOT_EQUAL)) {
      return rhs.binaryOps(op, this);
    }

    if (rhs.type() == SelTypes.DOUBLE) {
      SelDouble lhs = SelDouble.of(this.val);
      return lhs.binaryOps(op, rhs);
    }

    if (rhs.type() == SelTypes.STRING) {
      return SelString.of(String.valueOf(this.val)).binaryOps(op, rhs);
    }

    SelTypeUtil.checkTypeMatch(this.type(), rhs.type());
    long another = ((SelLong) rhs).val;
    switch (op) {
      case EQUAL:
        return SelBoolean.of(this.val == another);
      case NOT_EQUAL:
        return SelBoolean.of(this.val != another);
      case LT:
        return SelBoolean.of(this.val < another);
      case GT:
        return SelBoolean.of(this.val > another);
      case LTE:
        return SelBoolean.of(this.val <= another);
      case GTE:
        return SelBoolean.of(this.val >= another);
      case ADD:
        return new SelLong(this.val + another);
      case SUB:
        return new SelLong(this.val - another);
      case MUL:
        return new SelLong(this.val * another);
      case DIV:
        return new SelLong(this.val / another);
      case MOD:
        return new SelLong(this.val % another);
      case PLUS:
        return new SelLong(this.val);
      case MINUS:
        return new SelLong(-this.val);
      default:
        throw new UnsupportedOperationException(
            "int/Integer/long/Long DO NOT support expression operation " + op);
    }
  }
@Test
  public void testBinaryOps() {
    SelType obj = SelLong.of(2);
    SelType res = orig.binaryOps(SelOp.EQUAL, obj);
    assertEquals("BOOLEAN: false", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.NOT_EQUAL, obj);
    assertEquals("BOOLEAN: true", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.LT, obj);
    assertEquals("BOOLEAN: true", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.GT, obj);
    assertEquals("BOOLEAN: false", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.LTE, obj);
    assertEquals("BOOLEAN: true", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.GTE, obj);
    assertEquals("BOOLEAN: false", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.ADD, obj);
    assertEquals("LONG: 3", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.SUB, obj);
    assertEquals("LONG: -1", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.MUL, obj);
    assertEquals("LONG: 2", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.DIV, obj);
    assertEquals("LONG: 0", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.MOD, obj);
    assertEquals("LONG: 1", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.PLUS, obj);
    assertEquals("LONG: 1", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.MINUS, obj);
    assertEquals("LONG: -1", res.type() + ": " + res);

    res = orig.binaryOps(SelOp.EQUAL, SelType.NULL);
    assertEquals("BOOLEAN: false", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.NOT_EQUAL, SelType.NULL);
    assertEquals("BOOLEAN: true", res.type() + ": " + res);

    res = orig.binaryOps(SelOp.ADD, SelDouble.of(2.0));
    assertEquals("DOUBLE: 3.0", res.type() + ": " + res);
    res = orig.binaryOps(SelOp.ADD, SelString.of("2"));
    assertEquals("STRING: 12", res.type() + ": " + res);
  }