content: /*
   * 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.sql2rel;
  
  import org.apache.calcite.rel.RelHomogeneousShuttle;
  import org.apache.calcite.rel.RelNode;
  import org.apache.calcite.rex.RexBuilder;
  import org.apache.calcite.rex.RexCall;
  import org.apache.calcite.rex.RexNode;
  import org.apache.calcite.rex.RexShuttle;
  import org.apache.calcite.rex.RexSubQuery;
  import org.apache.calcite.sql.SqlKind;
  import org.apache.calcite.sql.SqlOperator;
  import org.apache.calcite.sql.fun.SqlStdOperatorTable;
  import org.apache.calcite.sql.type.SqlTypeName;
  
  import java.util.List;
  
  /**
   * Converts a RelNode tree such that numeric arithmetic operations
   * use checked arithmetic.  Most SQL dialects should use checked arithmetic,
   * i.e., they should produce a runtime error on overflow.
   */
  public class ConvertToChecked extends RelHomogeneousShuttle {
    final ConvertRexToChecked converter;
  
    public ConvertToChecked(RexBuilder builder) {
      this.converter = new ConvertRexToChecked(builder);
    }
  
    @Override public RelNode visit(RelNode other) {
      RelNode mNode = super.visitChildren(other);
      return mNode.accept(converter);
    }
  
    /**
     * Visitor which rewrites an expression tree such that all
     * arithmetic operations that produce numeric values use checked arithmetic.
     */
    class ConvertRexToChecked extends RexShuttle {
      private final RexBuilder builder;
  
      ConvertRexToChecked(RexBuilder builder) {
        this.builder = builder;
      }
  
      @Override public RexNode visitSubQuery(RexSubQuery subQuery) {
        RelNode result = subQuery.rel.accept(ConvertToChecked.this);
        if (result != subQuery.rel) {
          return subQuery.clone(result);
        } else {
          return subQuery;
        }
      }
  
      @Override public RexNode visitCall(final RexCall call) {
        boolean[] update = {false};
        List<RexNode> clonedOperands = visitList(call.operands, update);
        SqlKind kind = call.getKind();
        SqlOperator operator = call.getOperator();
        switch (kind) {
        case PLUS:
          operator = SqlStdOperatorTable.CHECKED_PLUS;
          break;
        case MINUS:
          operator = SqlStdOperatorTable.CHECKED_MINUS;
          break;
        case TIMES:
          operator = SqlStdOperatorTable.CHECKED_MULTIPLY;
          break;
        case MINUS_PREFIX:
          operator = SqlStdOperatorTable.CHECKED_UNARY_MINUS;
          break;
        case DIVIDE:
          operator = SqlStdOperatorTable.CHECKED_DIVIDE;
          break;
        default:
          break;
        }
        SqlTypeName resultType = call.getType().getSqlTypeName();
        if (resultType == SqlTypeName.DECIMAL) {
          // Checked decimal arithmetic is implemented using unchecked
          // arithmetic followed by a CAST, which is always checked
          RexCall result;
          if (operator == call.getOperator()) {
            result = call.clone(call.getType(), clonedOperands);
          } else {
            result = call;
          }
          return builder.makeCast(call.getType(), result);
        } else if (!SqlTypeName.EXACT_TYPES.contains(resultType)) {
          // Do not rewrite operator if the type is e.g., DOUBLE or DATE
          operator = call.getOperator();
        }
        update[0] = update[0] || operator != call.getOperator();
        if (update[0]) {
          if (operator == call.getOperator()) {
            return call.clone(call.getType(), clonedOperands);
          } else {
            return builder.makeCall(
                call.getParserPosition(), call.getType(), operator, clonedOperands);
          }
        } else {
          return call;
        }
      }
    }
  }
  