/*
 * 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.type;

import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.util.DateString;
import org.apache.calcite.util.TimeString;
import org.apache.calcite.util.TimestampString;
import org.apache.calcite.util.Util;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

import java.math.BigDecimal;
import java.sql.Types;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Enumeration of the type names which can be used to construct a SQL type.
 * Rationale for this class's existence (instead of just using the standard
 * java.sql.Type ordinals):
 *
 * <ul>
 * <li>{@link java.sql.Types} does not include all SQL2003 data-types;
 * <li>SqlTypeName provides a type-safe enumeration;
 * <li>SqlTypeName provides a place to hang extra information such as whether
 * the type carries precision and scale.
 * </ul>
 */
public enum SqlTypeName {
  BOOLEAN(PrecScale.NO_NO, false, Types.BOOLEAN, SqlTypeFamily.BOOLEAN),
  TINYINT(PrecScale.NO_NO, false, Types.TINYINT, SqlTypeFamily.NUMERIC),
  SMALLINT(PrecScale.NO_NO, false, Types.SMALLINT, SqlTypeFamily.NUMERIC),
  INTEGER(PrecScale.NO_NO, false, Types.INTEGER, SqlTypeFamily.NUMERIC),
  BIGINT(PrecScale.NO_NO, false, Types.BIGINT, SqlTypeFamily.NUMERIC),
  DECIMAL(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false,
      Types.DECIMAL, SqlTypeFamily.NUMERIC),
  FLOAT(PrecScale.NO_NO, false, Types.FLOAT, SqlTypeFamily.NUMERIC),
  REAL(PrecScale.NO_NO, false, Types.REAL, SqlTypeFamily.NUMERIC),
  DOUBLE(PrecScale.NO_NO, false, Types.DOUBLE, SqlTypeFamily.NUMERIC),
  DATE(PrecScale.NO_NO, false, Types.DATE, SqlTypeFamily.DATE),
  TIME(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.TIME,
      SqlTypeFamily.TIME),
  TIME_WITH_LOCAL_TIME_ZONE(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.OTHER,
      SqlTypeFamily.TIME),
  TIMESTAMP(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.TIMESTAMP,
      SqlTypeFamily.TIMESTAMP),
  TIMESTAMP_WITH_LOCAL_TIME_ZONE(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.OTHER,
      SqlTypeFamily.TIMESTAMP),
  INTERVAL_YEAR(PrecScale.NO_NO, false, Types.OTHER,
      SqlTypeFamily.INTERVAL_YEAR_MONTH),
  INTERVAL_YEAR_MONTH(PrecScale.NO_NO, false, Types.OTHER,
      SqlTypeFamily.INTERVAL_YEAR_MONTH),
  INTERVAL_MONTH(PrecScale.NO_NO, false, Types.OTHER,
      SqlTypeFamily.INTERVAL_YEAR_MONTH),
  INTERVAL_DAY(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  INTERVAL_DAY_HOUR(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  INTERVAL_DAY_MINUTE(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  INTERVAL_DAY_SECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  INTERVAL_HOUR(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  INTERVAL_HOUR_MINUTE(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  INTERVAL_HOUR_SECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  INTERVAL_MINUTE(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  INTERVAL_MINUTE_SECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  INTERVAL_SECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES,
      false, Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
  CHAR(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.CHAR,
      SqlTypeFamily.CHARACTER),
  VARCHAR(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.VARCHAR,
      SqlTypeFamily.CHARACTER),
  BINARY(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.BINARY,
      SqlTypeFamily.BINARY),
  VARBINARY(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.VARBINARY,
      SqlTypeFamily.BINARY),
  NULL(PrecScale.NO_NO, true, Types.NULL, SqlTypeFamily.NULL),
  ANY(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, true,
      Types.JAVA_OBJECT, SqlTypeFamily.ANY),
  SYMBOL(PrecScale.NO_NO, true, Types.OTHER, null),
  MULTISET(PrecScale.NO_NO, false, Types.ARRAY, SqlTypeFamily.MULTISET),
  ARRAY(PrecScale.NO_NO, false, Types.ARRAY, SqlTypeFamily.ARRAY),
  MAP(PrecScale.NO_NO, false, Types.OTHER, SqlTypeFamily.MAP),
  DISTINCT(PrecScale.NO_NO, false, Types.DISTINCT, null),
  STRUCTURED(PrecScale.NO_NO, false, Types.STRUCT, null),
  ROW(PrecScale.NO_NO, false, Types.STRUCT, null),
  OTHER(PrecScale.NO_NO, false, Types.OTHER, null),
  CURSOR(PrecScale.NO_NO, false, ExtraSqlTypes.REF_CURSOR,
      SqlTypeFamily.CURSOR),
  COLUMN_LIST(PrecScale.NO_NO, false, Types.OTHER + 2,
      SqlTypeFamily.COLUMN_LIST),
  DYNAMIC_STAR(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, true,
      Types.JAVA_OBJECT, SqlTypeFamily.ANY),
  GEOMETRY(PrecScale.NO_NO, true, ExtraSqlTypes.GEOMETRY, SqlTypeFamily.GEO);

  public static final int MAX_DATETIME_PRECISION = 3;

  // Minimum and default interval precisions are  defined by SQL2003
  // Maximum interval precisions are implementation dependent,
  //  but must be at least the default value
  public static final int DEFAULT_INTERVAL_START_PRECISION = 2;
  public static final int DEFAULT_INTERVAL_FRACTIONAL_SECOND_PRECISION = 6;
  public static final int MIN_INTERVAL_START_PRECISION = 1;
  public static final int MIN_INTERVAL_FRACTIONAL_SECOND_PRECISION = 1;
  public static final int MAX_INTERVAL_START_PRECISION = 10;
  public static final int MAX_INTERVAL_FRACTIONAL_SECOND_PRECISION = 9;

  // Cached map of enum values
  private static final Map<String, SqlTypeName> VALUES_MAP =
      Util.enumConstants(SqlTypeName.class);

  // categorizations used by SqlTypeFamily definitions

  // you probably want to use JDK 1.5 support for treating enumeration
  // as collection instead; this is only here to support
  // SqlTypeFamily.ANY
  public static final List<SqlTypeName> ALL_TYPES =
      ImmutableList.of(
          BOOLEAN, INTEGER, VARCHAR, DATE, TIME, TIMESTAMP, NULL, DECIMAL,
          ANY, CHAR, BINARY, VARBINARY, TINYINT, SMALLINT, BIGINT, REAL,
          DOUBLE, SYMBOL, INTERVAL_YEAR, INTERVAL_YEAR_MONTH, INTERVAL_MONTH,
          INTERVAL_DAY, INTERVAL_DAY_HOUR, INTERVAL_DAY_MINUTE,
          INTERVAL_DAY_SECOND, INTERVAL_HOUR, INTERVAL_HOUR_MINUTE,
          INTERVAL_HOUR_SECOND, INTERVAL_MINUTE, INTERVAL_MINUTE_SECOND,
          INTERVAL_SECOND, TIME_WITH_LOCAL_TIME_ZONE, TIMESTAMP_WITH_LOCAL_TIME_ZONE,
          FLOAT, MULTISET, DISTINCT, STRUCTURED, ROW, CURSOR, COLUMN_LIST);

  public static final List<SqlTypeName> BOOLEAN_TYPES =
      ImmutableList.of(BOOLEAN);

  public static final List<SqlTypeName> BINARY_TYPES =
      ImmutableList.of(BINARY, VARBINARY);

  public static final List<SqlTypeName> INT_TYPES =
      ImmutableList.of(TINYINT, SMALLINT, INTEGER, BIGINT);

  public static final List<SqlTypeName> EXACT_TYPES =
      combine(INT_TYPES, ImmutableList.of(DECIMAL));

  public static final List<SqlTypeName> APPROX_TYPES =
      ImmutableList.of(FLOAT, REAL, DOUBLE);

  public static final List<SqlTypeName> NUMERIC_TYPES =
      combine(EXACT_TYPES, APPROX_TYPES);

  public static final List<SqlTypeName> FRACTIONAL_TYPES =
      combine(APPROX_TYPES, ImmutableList.of(DECIMAL));

  public static final List<SqlTypeName> CHAR_TYPES =
      ImmutableList.of(CHAR, VARCHAR);

  public static final List<SqlTypeName> STRING_TYPES =
      combine(CHAR_TYPES, BINARY_TYPES);

  public static final List<SqlTypeName> DATETIME_TYPES =
      ImmutableList.of(DATE, TIME, TIME_WITH_LOCAL_TIME_ZONE,
          TIMESTAMP, TIMESTAMP_WITH_LOCAL_TIME_ZONE);

  public static final Set<SqlTypeName> YEAR_INTERVAL_TYPES =
      Sets.immutableEnumSet(SqlTypeName.INTERVAL_YEAR,
          SqlTypeName.INTERVAL_YEAR_MONTH,
          SqlTypeName.INTERVAL_MONTH);

  public static final Set<SqlTypeName> DAY_INTERVAL_TYPES =
      Sets.immutableEnumSet(SqlTypeName.INTERVAL_DAY,
          SqlTypeName.INTERVAL_DAY_HOUR,
          SqlTypeName.INTERVAL_DAY_MINUTE,
          SqlTypeName.INTERVAL_DAY_SECOND,
          SqlTypeName.INTERVAL_HOUR,
          SqlTypeName.INTERVAL_HOUR_MINUTE,
          SqlTypeName.INTERVAL_HOUR_SECOND,
          SqlTypeName.INTERVAL_MINUTE,
          SqlTypeName.INTERVAL_MINUTE_SECOND,
          SqlTypeName.INTERVAL_SECOND);

  public static final Set<SqlTypeName> INTERVAL_TYPES =
      Sets.immutableEnumSet(
          Iterables.concat(YEAR_INTERVAL_TYPES, DAY_INTERVAL_TYPES));

  private static final Map<Integer, SqlTypeName> JDBC_TYPE_TO_NAME =
      ImmutableMap.<Integer, SqlTypeName>builder()
          .put(Types.TINYINT, TINYINT)
          .put(Types.SMALLINT, SMALLINT)
          .put(Types.BIGINT, BIGINT)
          .put(Types.INTEGER, INTEGER)
          .put(Types.NUMERIC, DECIMAL) // REVIEW
          .put(Types.DECIMAL, DECIMAL)

          .put(Types.FLOAT, FLOAT)
          .put(Types.REAL, REAL)
          .put(Types.DOUBLE, DOUBLE)

          .put(Types.CHAR, CHAR)
          .put(Types.VARCHAR, VARCHAR)

              // TODO: provide real support for these eventually
          .put(ExtraSqlTypes.NCHAR, CHAR)
          .put(ExtraSqlTypes.NVARCHAR, VARCHAR)

              // TODO: additional types not yet supported. See ExtraSqlTypes.
              // .put(Types.LONGVARCHAR, Longvarchar)
              // .put(Types.CLOB, Clob)
              // .put(Types.LONGVARBINARY, Longvarbinary)
              // .put(Types.BLOB, Blob)
              // .put(Types.LONGNVARCHAR, Longnvarchar)
              // .put(Types.NCLOB, Nclob)
              // .put(Types.ROWID, Rowid)
              // .put(Types.SQLXML, Sqlxml)

          .put(Types.BINARY, BINARY)
          .put(Types.VARBINARY, VARBINARY)

          .put(Types.DATE, DATE)
          .put(Types.TIME, TIME)
          .put(Types.TIMESTAMP, TIMESTAMP)
          .put(Types.BIT, BOOLEAN)
          .put(Types.BOOLEAN, BOOLEAN)
          .put(Types.DISTINCT, DISTINCT)
          .put(Types.STRUCT, STRUCTURED)
          .put(Types.ARRAY, ARRAY)
          .build();

  /**
   * Bitwise-or of flags indicating allowable precision/scale combinations.
   */
  private final int signatures;

  /**
   * Returns true if not of a "pure" standard sql type. "Inpure" types are
   * {@link #ANY}, {@link #NULL} and {@link #SYMBOL}
   */
  private final boolean special;
  private final int jdbcOrdinal;
  private final SqlTypeFamily family;

  SqlTypeName(int signatures, boolean special, int jdbcType,
      SqlTypeFamily family) {
    this.signatures = signatures;
    this.special = special;
    this.jdbcOrdinal = jdbcType;
    this.family = family;
  }

  /**
   * Looks up a type name from its name.
   *
   * @return Type name, or null if not found
   */
  public static SqlTypeName get(String name) {
    if (false) {
      // The following code works OK, but the spurious exceptions are
      // annoying.
      try {
        return SqlTypeName.valueOf(name);
      } catch (IllegalArgumentException e) {
        return null;
      }
    }
    return VALUES_MAP.get(name);
  }

  public boolean allowsNoPrecNoScale() {
    return (signatures & PrecScale.NO_NO) != 0;
  }

  public boolean allowsPrecNoScale() {
    return (signatures & PrecScale.YES_NO) != 0;
  }

  public boolean allowsPrec() {
    return allowsPrecScale(true, true)
        || allowsPrecScale(true, false);
  }

  public boolean allowsScale() {
    return allowsPrecScale(true, true);
  }

  /**
   * Returns whether this type can be specified with a given combination of
   * precision and scale. For example,
   *
   * <ul>
   * <li><code>Varchar.allowsPrecScale(true, false)</code> returns <code>
   * true</code>, because the VARCHAR type allows a precision parameter, as in
   * <code>VARCHAR(10)</code>.</li>
   * <li><code>Varchar.allowsPrecScale(true, true)</code> returns <code>
   * true</code>, because the VARCHAR type does not allow a precision and a
   * scale parameter, as in <code>VARCHAR(10, 4)</code>.</li>
   * <li><code>allowsPrecScale(false, true)</code> returns <code>false</code>
   * for every type.</li>
   * </ul>
   *
   * @param precision Whether the precision/length field is part of the type
   *                  specification
   * @param scale     Whether the scale field is part of the type specification
   * @return Whether this combination of precision/scale is valid
   */
  public boolean allowsPrecScale(
      boolean precision,
      boolean scale) {
    int mask =
        precision ? (scale ? PrecScale.YES_YES : PrecScale.YES_NO)
            : (scale ? 0 : PrecScale.NO_NO);
    return (signatures & mask) != 0;
  }

  public boolean isSpecial() {
    return special;
  }

  /**
   * @return the ordinal from {@link java.sql.Types} corresponding to this
   * SqlTypeName
   */
  public int getJdbcOrdinal() {
    return jdbcOrdinal;
  }

  private static List<SqlTypeName> combine(
      List<SqlTypeName> list0,
      List<SqlTypeName> list1) {
    return ImmutableList.<SqlTypeName>builder()
        .addAll(list0)
        .addAll(list1)
        .build();
  }

  /**
   * @return default scale for this type if supported, otherwise -1 if scale
   * is either unsupported or must be specified explicitly
   */
  public int getDefaultScale() {
    switch (this) {
    case DECIMAL:
      return 0;
    case INTERVAL_YEAR:
    case INTERVAL_YEAR_MONTH:
    case INTERVAL_MONTH:
    case INTERVAL_DAY:
    case INTERVAL_DAY_HOUR:
    case INTERVAL_DAY_MINUTE:
    case INTERVAL_DAY_SECOND:
    case INTERVAL_HOUR:
    case INTERVAL_HOUR_MINUTE:
    case INTERVAL_HOUR_SECOND:
    case INTERVAL_MINUTE:
    case INTERVAL_MINUTE_SECOND:
    case INTERVAL_SECOND:
      return DEFAULT_INTERVAL_FRACTIONAL_SECOND_PRECISION;
    default:
      return -1;
    }
  }

  /**
   * Gets the SqlTypeFamily containing this SqlTypeName.
   *
   * @return containing family, or null for none
   */
  public SqlTypeFamily getFamily() {
    return family;
  }

  /**
   * Gets the SqlTypeName corresponding to a JDBC type.
   *
   * @param jdbcType the JDBC type of interest
   * @return corresponding SqlTypeName, or null if the type is not known
   */
  public static SqlTypeName getNameForJdbcType(int jdbcType) {
    return JDBC_TYPE_TO_NAME.get(jdbcType);
  }

  /**
   * Returns the limit of this datatype. For example,
   *
   * <table border="1">
   * <caption>Datatype limits</caption>
   * <tr>
   * <th>Datatype</th>
   * <th>sign</th>
   * <th>limit</th>
   * <th>beyond</th>
   * <th>precision</th>
   * <th>scale</th>
   * <th>Returns</th>
   * </tr>
   * <tr>
   * <td>Integer</td>
   * <td>true</td>
   * <td>true</td>
   * <td>false</td>
   * <td>-1</td>
   * <td>-1</td>
   * <td>2147483647 (2 ^ 31 -1 = MAXINT)</td>
   * </tr>
   * <tr>
   * <td>Integer</td>
   * <td>true</td>
   * <td>true</td>
   * <td>true</td>
   * <td>-1</td>
   * <td>-1</td>
   * <td>2147483648 (2 ^ 31 = MAXINT + 1)</td>
   * </tr>
   * <tr>
   * <td>Integer</td>
   * <td>false</td>
   * <td>true</td>
   * <td>false</td>
   * <td>-1</td>
   * <td>-1</td>
   * <td>-2147483648 (-2 ^ 31 = MININT)</td>
   * </tr>
   * <tr>
   * <td>Boolean</td>
   * <td>true</td>
   * <td>true</td>
   * <td>false</td>
   * <td>-1</td>
   * <td>-1</td>
   * <td>TRUE</td>
   * </tr>
   * <tr>
   * <td>Varchar</td>
   * <td>true</td>
   * <td>true</td>
   * <td>false</td>
   * <td>10</td>
   * <td>-1</td>
   * <td>'ZZZZZZZZZZ'</td>
   * </tr>
   * </table>
   *
   * @param sign      If true, returns upper limit, otherwise lower limit
   * @param limit     If true, returns value at or near to overflow; otherwise
   *                  value at or near to underflow
   * @param beyond    If true, returns the value just beyond the limit,
   *                  otherwise the value at the limit
   * @param precision Precision, or -1 if not applicable
   * @param scale     Scale, or -1 if not applicable
   * @return Limit value
   */
  public Object getLimit(
      boolean sign,
      Limit limit,
      boolean beyond,
      int precision,
      int scale) {
    assert allowsPrecScale(precision != -1, scale != -1) : this;
    if (limit == Limit.ZERO) {
      if (beyond) {
        return null;
      }
      sign = true;
    }
    Calendar calendar;

    switch (this) {
    case BOOLEAN:
      switch (limit) {
      case ZERO:
        return false;
      case UNDERFLOW:
        return null;
      case OVERFLOW:
        if (beyond || !sign) {
          return null;
        } else {
          return true;
        }
      default:
        throw Util.unexpected(limit);
      }

    case TINYINT:
      return getNumericLimit(2, 8, sign, limit, beyond);

    case SMALLINT:
      return getNumericLimit(2, 16, sign, limit, beyond);

    case INTEGER:
      return getNumericLimit(2, 32, sign, limit, beyond);

    case BIGINT:
      return getNumericLimit(2, 64, sign, limit, beyond);

    case DECIMAL:
      BigDecimal decimal =
          getNumericLimit(10, precision, sign, limit, beyond);
      if (decimal == null) {
        return null;
      }

      // Decimal values must fit into 64 bits. So, the maximum value of
      // a DECIMAL(19, 0) is 2^63 - 1, not 10^19 - 1.
      switch (limit) {
      case OVERFLOW:
        final BigDecimal other =
            (BigDecimal) BIGINT.getLimit(sign, limit, beyond, -1, -1);
        if (decimal.compareTo(other) == (sign ? 1 : -1)) {
          decimal = other;
        }
      }

      // Apply scale.
      if (scale == 0) {
        // do nothing
      } else if (scale > 0) {
        decimal = decimal.divide(BigDecimal.TEN.pow(scale));
      } else {
        decimal = decimal.multiply(BigDecimal.TEN.pow(-scale));
      }
      return decimal;

    case CHAR:
    case VARCHAR:
      if (!sign) {
        return null; // this type does not have negative values
      }
      StringBuilder buf = new StringBuilder();
      switch (limit) {
      case ZERO:
        break;
      case UNDERFLOW:
        if (beyond) {
          // There is no value between the empty string and the
          // smallest non-empty string.
          return null;
        }
        buf.append("a");
        break;
      case OVERFLOW:
        for (int i = 0; i < precision; ++i) {
          buf.append("Z");
        }
        if (beyond) {
          buf.append("Z");
        }
        break;
      }
      return buf.toString();

    case BINARY:
    case VARBINARY:
      if (!sign) {
        return null; // this type does not have negative values
      }
      byte[] bytes;
      switch (limit) {
      case ZERO:
        bytes = new byte[0];
        break;
      case UNDERFLOW:
        if (beyond) {
          // There is no value between the empty string and the
          // smallest value.
          return null;
        }
        bytes = new byte[]{0x00};
        break;
      case OVERFLOW:
        bytes = new byte[precision + (beyond ? 1 : 0)];
        Arrays.fill(bytes, (byte) 0xff);
        break;
      default:
        throw Util.unexpected(limit);
      }
      return bytes;

    case DATE:
      calendar = Util.calendar();
      switch (limit) {
      case ZERO:

        // The epoch.
        calendar.set(Calendar.YEAR, 1970);
        calendar.set(Calendar.MONTH, 0);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        break;
      case UNDERFLOW:
        return null;
      case OVERFLOW:
        if (beyond) {
          // It is impossible to represent an invalid year as a date
          // literal. SQL dates are represented as 'yyyy-mm-dd', and
          // 1 <= yyyy <= 9999 is valid. There is no year 0: the year
          // before 1AD is 1BC, so SimpleDateFormat renders the day
          // before 0001-01-01 (AD) as 0001-12-31 (BC), which looks
          // like a valid date.
          return null;
        }

        // "SQL:2003 6.1 <data type> Access Rules 6" says that year is
        // between 1 and 9999, and days/months are the valid Gregorian
        // calendar values for these years.
        if (sign) {
          calendar.set(Calendar.YEAR, 9999);
          calendar.set(Calendar.MONTH, 11);
          calendar.set(Calendar.DAY_OF_MONTH, 31);
        } else {
          calendar.set(Calendar.YEAR, 1);
          calendar.set(Calendar.MONTH, 0);
          calendar.set(Calendar.DAY_OF_MONTH, 1);
        }
        break;
      }
      calendar.set(Calendar.HOUR_OF_DAY, 0);
      calendar.set(Calendar.MINUTE, 0);
      calendar.set(Calendar.SECOND, 0);
      return calendar;

    case TIME:
      if (!sign) {
        return null; // this type does not have negative values
      }
      if (beyond) {
        return null; // invalid values are impossible to represent
      }
      calendar = Util.calendar();
      switch (limit) {
      case ZERO:

        // The epoch.
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        break;
      case UNDERFLOW:
        return null;
      case OVERFLOW:
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        int millis =
            (precision >= 3) ? 999
                : ((precision == 2) ? 990 : ((precision == 1) ? 900 : 0));
        calendar.set(Calendar.MILLISECOND, millis);
        break;
      }
      return calendar;

    case TIMESTAMP:
      calendar = Util.calendar();
      switch (limit) {
      case ZERO:

        // The epoch.
        calendar.set(Calendar.YEAR, 1970);
        calendar.set(Calendar.MONTH, 0);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        break;
      case UNDERFLOW:
        return null;
      case OVERFLOW:
        if (beyond) {
          // It is impossible to represent an invalid year as a date
          // literal. SQL dates are represented as 'yyyy-mm-dd', and
          // 1 <= yyyy <= 9999 is valid. There is no year 0: the year
          // before 1AD is 1BC, so SimpleDateFormat renders the day
          // before 0001-01-01 (AD) as 0001-12-31 (BC), which looks
          // like a valid date.
          return null;
        }

        // "SQL:2003 6.1 <data type> Access Rules 6" says that year is
        // between 1 and 9999, and days/months are the valid Gregorian
        // calendar values for these years.
        if (sign) {
          calendar.set(Calendar.YEAR, 9999);
          calendar.set(Calendar.MONTH, 11);
          calendar.set(Calendar.DAY_OF_MONTH, 31);
          calendar.set(Calendar.HOUR_OF_DAY, 23);
          calendar.set(Calendar.MINUTE, 59);
          calendar.set(Calendar.SECOND, 59);
          int millis =
              (precision >= 3) ? 999
                  : ((precision == 2) ? 990
                      : ((precision == 1) ? 900 : 0));
          calendar.set(Calendar.MILLISECOND, millis);
        } else {
          calendar.set(Calendar.YEAR, 1);
          calendar.set(Calendar.MONTH, 0);
          calendar.set(Calendar.DAY_OF_MONTH, 1);
          calendar.set(Calendar.HOUR_OF_DAY, 0);
          calendar.set(Calendar.MINUTE, 0);
          calendar.set(Calendar.SECOND, 0);
          calendar.set(Calendar.MILLISECOND, 0);
        }
        break;
      }
      return calendar;

    default:
      throw Util.unexpected(this);
    }
  }

  /**
   * Returns the minimum precision (or length) allowed for this type, or -1 if
   * precision/length are not applicable for this type.
   *
   * @return Minimum allowed precision
   */
  public int getMinPrecision() {
    switch (this) {
    case DECIMAL:
    case VARCHAR:
    case CHAR:
    case VARBINARY:
    case BINARY:
    case TIME:
    case TIME_WITH_LOCAL_TIME_ZONE:
    case TIMESTAMP:
    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
      return 1;
    case INTERVAL_YEAR:
    case INTERVAL_YEAR_MONTH:
    case INTERVAL_MONTH:
    case INTERVAL_DAY:
    case INTERVAL_DAY_HOUR:
    case INTERVAL_DAY_MINUTE:
    case INTERVAL_DAY_SECOND:
    case INTERVAL_HOUR:
    case INTERVAL_HOUR_MINUTE:
    case INTERVAL_HOUR_SECOND:
    case INTERVAL_MINUTE:
    case INTERVAL_MINUTE_SECOND:
    case INTERVAL_SECOND:
      return MIN_INTERVAL_START_PRECISION;
    default:
      return -1;
    }
  }

  /**
   * Returns the minimum scale (or fractional second precision in the case of
   * intervals) allowed for this type, or -1 if precision/length are not
   * applicable for this type.
   *
   * @return Minimum allowed scale
   */
  public int getMinScale() {
    switch (this) {
    // TODO: Minimum numeric scale for decimal
    case INTERVAL_YEAR:
    case INTERVAL_YEAR_MONTH:
    case INTERVAL_MONTH:
    case INTERVAL_DAY:
    case INTERVAL_DAY_HOUR:
    case INTERVAL_DAY_MINUTE:
    case INTERVAL_DAY_SECOND:
    case INTERVAL_HOUR:
    case INTERVAL_HOUR_MINUTE:
    case INTERVAL_HOUR_SECOND:
    case INTERVAL_MINUTE:
    case INTERVAL_MINUTE_SECOND:
    case INTERVAL_SECOND:
      return MIN_INTERVAL_FRACTIONAL_SECOND_PRECISION;
    default:
      return -1;
    }
  }

  /** Returns {@code HOUR} for {@code HOUR TO SECOND} and
   * {@code HOUR}, {@code SECOND} for {@code SECOND}. */
  public TimeUnit getStartUnit() {
    switch (this) {
    case INTERVAL_YEAR:
    case INTERVAL_YEAR_MONTH:
      return TimeUnit.YEAR;
    case INTERVAL_MONTH:
      return TimeUnit.MONTH;
    case INTERVAL_DAY:
    case INTERVAL_DAY_HOUR:
    case INTERVAL_DAY_MINUTE:
    case INTERVAL_DAY_SECOND:
      return TimeUnit.DAY;
    case INTERVAL_HOUR:
    case INTERVAL_HOUR_MINUTE:
    case INTERVAL_HOUR_SECOND:
      return TimeUnit.HOUR;
    case INTERVAL_MINUTE:
    case INTERVAL_MINUTE_SECOND:
      return TimeUnit.MINUTE;
    case INTERVAL_SECOND:
      return TimeUnit.SECOND;
    default:
      throw new AssertionError(this);
    }
  }

  /** Returns {@code SECOND} for both {@code HOUR TO SECOND} and
   * {@code SECOND}. */
  public TimeUnit getEndUnit() {
    switch (this) {
    case INTERVAL_YEAR:
      return TimeUnit.YEAR;
    case INTERVAL_YEAR_MONTH:
    case INTERVAL_MONTH:
      return TimeUnit.MONTH;
    case INTERVAL_DAY:
      return TimeUnit.DAY;
    case INTERVAL_DAY_HOUR:
    case INTERVAL_HOUR:
      return TimeUnit.HOUR;
    case INTERVAL_DAY_MINUTE:
    case INTERVAL_HOUR_MINUTE:
    case INTERVAL_MINUTE:
      return TimeUnit.MINUTE;
    case INTERVAL_DAY_SECOND:
    case INTERVAL_HOUR_SECOND:
    case INTERVAL_MINUTE_SECOND:
    case INTERVAL_SECOND:
      return TimeUnit.SECOND;
    default:
      throw new AssertionError(this);
    }
  }

  public boolean isYearMonth() {
    switch (this) {
    case INTERVAL_YEAR:
    case INTERVAL_YEAR_MONTH:
    case INTERVAL_MONTH:
      return true;
    default:
      return false;
    }
  }

  /** Limit. */
  public enum Limit {
    ZERO, UNDERFLOW, OVERFLOW
  }

  private BigDecimal getNumericLimit(
      int radix,
      int exponent,
      boolean sign,
      Limit limit,
      boolean beyond) {
    switch (limit) {
    case OVERFLOW:

      // 2-based schemes run from -2^(N-1) to 2^(N-1)-1 e.g. -128 to +127
      // 10-based schemas run from -(10^N-1) to 10^N-1 e.g. -99 to +99
      final BigDecimal bigRadix = BigDecimal.valueOf(radix);
      if (radix == 2) {
        --exponent;
      }
      BigDecimal decimal = bigRadix.pow(exponent);
      if (sign || (radix != 2)) {
        decimal = decimal.subtract(BigDecimal.ONE);
      }
      if (beyond) {
        decimal = decimal.add(BigDecimal.ONE);
      }
      if (!sign) {
        decimal = decimal.negate();
      }
      return decimal;
    case UNDERFLOW:
      return beyond ? null
          : (sign ? BigDecimal.ONE : BigDecimal.ONE.negate());
    case ZERO:
      return BigDecimal.ZERO;
    default:
      throw Util.unexpected(limit);
    }
  }

  public SqlLiteral createLiteral(Object o, SqlParserPos pos) {
    switch (this) {
    case BOOLEAN:
      return SqlLiteral.createBoolean((Boolean) o, pos);
    case TINYINT:
    case SMALLINT:
    case INTEGER:
    case BIGINT:
    case DECIMAL:
      return SqlLiteral.createExactNumeric(o.toString(), pos);
    case VARCHAR:
    case CHAR:
      return SqlLiteral.createCharString((String) o, pos);
    case VARBINARY:
    case BINARY:
      return SqlLiteral.createBinaryString((byte[]) o, pos);
    case DATE:
      return SqlLiteral.createDate(o instanceof Calendar
          ? DateString.fromCalendarFields((Calendar) o)
          : (DateString) o, pos);
    case TIME:
      return SqlLiteral.createTime(o instanceof Calendar
          ? TimeString.fromCalendarFields((Calendar) o)
          : (TimeString) o, 0 /* todo */, pos);
    case TIMESTAMP:
      return SqlLiteral.createTimestamp(o instanceof Calendar
          ? TimestampString.fromCalendarFields((Calendar) o)
          : (TimestampString) o, 0 /* todo */, pos);
    default:
      throw Util.unexpected(this);
    }
  }

  /**
   * @return name of this type
   */
  public String getName() {
    return toString();
  }

  /**
   * Flags indicating precision/scale combinations.
   *
   * <p>Note: for intervals:
   *
   * <ul>
   * <li>precision = start (leading field) precision</li>
   * <li>scale = fractional second precision</li>
   * </ul>
   */
  private interface PrecScale {
    int NO_NO = 1;
    int YES_NO = 2;
    int YES_YES = 4;
  }
}

// End SqlTypeName.java
