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.sql.fun;
  
  import org.apache.calcite.linq4j.Ord;
  import org.apache.calcite.rel.type.RelDataType;
  import org.apache.calcite.sql.SqlCallBinding;
  import org.apache.calcite.sql.SqlFunction;
  import org.apache.calcite.sql.SqlFunctionCategory;
  import org.apache.calcite.sql.SqlKind;
  import org.apache.calcite.sql.SqlOperandCountRange;
  import org.apache.calcite.sql.SqlOperatorBinding;
  import org.apache.calcite.sql.SqlUtil;
  import org.apache.calcite.sql.type.OperandTypes;
  import org.apache.calcite.sql.type.ReturnTypes;
  import org.apache.calcite.sql.type.SqlOperandCountRanges;
  import org.apache.calcite.sql.type.SqlSingleOperandTypeChecker;
  import org.apache.calcite.sql.type.SqlTypeName;
  import org.apache.calcite.sql.type.SqlTypeUtil;
  import org.apache.calcite.sql.validate.SqlMonotonicity;
  
  import com.google.common.collect.ImmutableList;
  
  import java.math.BigDecimal;
  import java.util.Objects;
  
  /**
   * Definition of the "SUBSTRING" builtin SQL function.
   */
  public class SqlSubstringFunction extends SqlFunction {
    /** Type checker for 3 argument calls. Put the STRING_INTEGER_INTEGER checker
     * first because almost every other type can be coerced to STRING. */
    private static final SqlSingleOperandTypeChecker CHECKER3 =
        OperandTypes.STRING_INTEGER_INTEGER;
            // Not yet implemented
            // .or(OperandTypes.STRING_STRING_STRING);
  
    //~ Constructors -----------------------------------------------------------
  
    /**
     * Creates the SqlSubstringFunction.
     */
    SqlSubstringFunction() {
      super(
          "SUBSTRING",
          SqlKind.OTHER_FUNCTION,
          ReturnTypes.ARG0_NULLABLE_VARYING,
          null,
          null,
          SqlFunctionCategory.STRING);
    }
  
    //~ Methods ----------------------------------------------------------------
  
    @Override public String getSignatureTemplate(final int operandsCount) {
      switch (operandsCount) {
      case 2:
        return "{0}({1} FROM {2})";
      case 3:
        return "{0}({1} FROM {2} FOR {3})";
      default:
        throw new AssertionError("Incorrect " + getName() + " signature, operands "
            + "count = " + operandsCount);
      }
    }
  
    @Override public String getAllowedSignatures(String opName) {
      StringBuilder ret = new StringBuilder();
      for (Ord<SqlTypeName> typeName : Ord.zip(SqlTypeName.STRING_TYPES)) {
        if (typeName.i > 0) {
          ret.append(NL);
        }
        ret.append(
            SqlUtil.getAliasedSignature(this, opName,
                ImmutableList.of(typeName.e, SqlTypeName.INTEGER)));
        ret.append(NL);
        ret.append(
            SqlUtil.getAliasedSignature(this, opName,
                ImmutableList.of(typeName.e, SqlTypeName.INTEGER,
                    SqlTypeName.INTEGER)));
      }
      return ret.toString();
    }
  
    @Override public boolean checkOperandTypes(
        SqlCallBinding callBinding,
        boolean throwOnFailure) {
      switch (callBinding.operands().size()) {
      default:
        throw new AssertionError();
      case 2:
        return OperandTypes.STRING_NUMERIC
            .checkOperandTypes(callBinding, throwOnFailure);
      case 3:
        if (!CHECKER3
            .checkOperandTypes(callBinding, throwOnFailure)) {
          if (throwOnFailure) {
            throw callBinding.newValidationSignatureError();
          }
          return false;
        }
        final RelDataType t1 = callBinding.getOperandType(1);
        final RelDataType t2 = callBinding.getOperandType(2);
        if (!(SqlTypeUtil.isIntType(t1) || SqlTypeUtil.isNull(t1))) {
          if (throwOnFailure) {
            throw callBinding.newValidationSignatureError();
          }
        }
        if (!SqlTypeUtil.inSameFamilyOrNull(t1, t2)) {
          if (throwOnFailure) {
            throw callBinding.newValidationSignatureError();
          }
          return false;
        }
        return true;
      }
    }
  
    @Override public SqlOperandCountRange getOperandCountRange() {
      return SqlOperandCountRanges.between(2, 3);
    }
  
    @Override public SqlMonotonicity getMonotonicity(SqlOperatorBinding call) {
      // SUBSTRING(x FROM 0 FOR constant) has same monotonicity as x
      if (call.getOperandCount() == 3) {
        final SqlMonotonicity mono0 = call.getOperandMonotonicity(0);
        if (mono0 != null
            && mono0 != SqlMonotonicity.NOT_MONOTONIC
            && call.getOperandMonotonicity(1) == SqlMonotonicity.CONSTANT
            && Objects.equals(call.getOperandLiteralValue(1, BigDecimal.class), BigDecimal.ZERO)
            && call.getOperandMonotonicity(2) == SqlMonotonicity.CONSTANT) {
          return mono0.unstrict();
        }
      }
      return super.getMonotonicity(call);
    }
  }
  