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

import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.linq4j.function.Function1;
import org.apache.calcite.linq4j.function.Functions;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypePrecedenceList;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.runtime.CalciteException;
import org.apache.calcite.runtime.PredicateImpl;
import org.apache.calcite.runtime.Resources;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.sql.util.SqlBasicVisitor;
import org.apache.calcite.util.BarfingInvocationHandler;
import org.apache.calcite.util.ConversionUtil;
import org.apache.calcite.util.Glossary;
import org.apache.calcite.util.NlsString;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;

import java.nio.charset.Charset;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static org.apache.calcite.util.Static.RESOURCE;

/**
 * Contains utility functions related to SQL parsing, all static.
 */
public abstract class SqlUtil {
  //~ Methods ----------------------------------------------------------------

  static SqlNode andExpressions(
      SqlNode node1,
      SqlNode node2) {
    if (node1 == null) {
      return node2;
    }
    ArrayList<SqlNode> list = new ArrayList<SqlNode>();
    if (node1.getKind() == SqlKind.AND) {
      list.addAll(((SqlCall) node1).getOperandList());
    } else {
      list.add(node1);
    }
    if (node2.getKind() == SqlKind.AND) {
      list.addAll(((SqlCall) node2).getOperandList());
    } else {
      list.add(node2);
    }
    return SqlStdOperatorTable.AND.createCall(
        SqlParserPos.ZERO,
        list);
  }

  static ArrayList<SqlNode> flatten(SqlNode node) {
    ArrayList<SqlNode> list = new ArrayList<SqlNode>();
    flatten(node, list);
    return list;
  }

  /**
   * Returns the <code>n</code>th (0-based) input to a join expression.
   */
  public static SqlNode getFromNode(
      SqlSelect query,
      int ordinal) {
    ArrayList<SqlNode> list = flatten(query.getFrom());
    return list.get(ordinal);
  }

  private static void flatten(
      SqlNode node,
      ArrayList<SqlNode> list) {
    switch (node.getKind()) {
    case JOIN:
      SqlJoin join = (SqlJoin) node;
      flatten(
          join.getLeft(),
          list);
      flatten(
          join.getRight(),
          list);
      return;
    case AS:
      SqlCall call = (SqlCall) node;
      flatten(call.operand(0), list);
      return;
    default:
      list.add(node);
      return;
    }
  }

  /**
   * Converts an SqlNode array to a SqlNodeList
   */
  public static SqlNodeList toNodeList(SqlNode[] operands) {
    SqlNodeList ret = new SqlNodeList(SqlParserPos.ZERO);
    for (SqlNode node : operands) {
      ret.add(node);
    }
    return ret;
  }

  /**
   * Returns whether a node represents the NULL value.
   *
   * <p>Examples:
   *
   * <ul>
   * <li>For {@link SqlLiteral} Unknown, returns false.
   * <li>For <code>CAST(NULL AS <i>type</i>)</code>, returns true if <code>
   * allowCast</code> is true, false otherwise.
   * <li>For <code>CAST(CAST(NULL AS <i>type</i>) AS <i>type</i>))</code>,
   * returns false.
   * </ul>
   */
  public static boolean isNullLiteral(
      SqlNode node,
      boolean allowCast) {
    if (node instanceof SqlLiteral) {
      SqlLiteral literal = (SqlLiteral) node;
      if (literal.getTypeName() == SqlTypeName.NULL) {
        assert null == literal.getValue();
        return true;
      } else {
        // We don't regard UNKNOWN -- SqlLiteral(null,Boolean) -- as
        // NULL.
        return false;
      }
    }
    if (allowCast) {
      if (node.getKind() == SqlKind.CAST) {
        SqlCall call = (SqlCall) node;
        if (isNullLiteral(call.operand(0), false)) {
          // node is "CAST(NULL as type)"
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Returns whether a node represents the NULL value or a series of nested
   * <code>CAST(NULL AS type)</code> calls. For example:
   * <code>isNull(CAST(CAST(NULL as INTEGER) AS VARCHAR(1)))</code>
   * returns {@code true}.
   */
  public static boolean isNull(SqlNode node) {
    return isNullLiteral(node, false)
        || node.getKind() == SqlKind.CAST
            && isNull(((SqlCall) node).operand(0));
  }

  /**
   * Returns whether a node is a literal.
   *
   * <p>Examples:
   *
   * <ul>
   * <li>For <code>CAST(literal AS <i>type</i>)</code>, returns true if <code>
   * allowCast</code> is true, false otherwise.
   * <li>For <code>CAST(CAST(literal AS <i>type</i>) AS <i>type</i>))</code>,
   * returns false.
   * </ul>
   *
   * @param node The node, never null.
   * @param allowCast whether to regard CAST(literal) as a literal
   * @return Whether the node is a literal
   */
  public static boolean isLiteral(SqlNode node, boolean allowCast) {
    assert node != null;
    if (node instanceof SqlLiteral) {
      return true;
    }
    if (allowCast) {
      if (node.getKind() == SqlKind.CAST) {
        SqlCall call = (SqlCall) node;
        if (isLiteral(call.operand(0), false)) {
          // node is "CAST(literal as type)"
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Returns whether a node is a literal.
   *
   * <p>Many constructs which require literals also accept <code>CAST(NULL AS
   * <i>type</i>)</code>. This method does not accept casts, so you should
   * call {@link #isNullLiteral} first.
   *
   * @param node The node, never null.
   * @return Whether the node is a literal
   */
  public static boolean isLiteral(SqlNode node) {
    return isLiteral(node, false);
  }

  /**
   * Returns whether a node is a literal chain which is used to represent a
   * continued string literal.
   *
   * @param node The node, never null.
   * @return Whether the node is a literal chain
   */
  public static boolean isLiteralChain(SqlNode node) {
    assert node != null;
    if (node instanceof SqlCall) {
      SqlCall call = (SqlCall) node;
      return call.getKind() == SqlKind.LITERAL_CHAIN;
    } else {
      return false;
    }
  }

  /**
   * Unparses a call to an operator which has function syntax.
   *
   * @param operator    The operator
   * @param writer      Writer
   * @param call    List of 0 or more operands
   */
  public static void unparseFunctionSyntax(
      SqlOperator operator,
      SqlWriter writer,
      SqlCall call) {
    if (operator instanceof SqlFunction) {
      SqlFunction function = (SqlFunction) operator;

      if (function.getFunctionType().isSpecific()) {
        writer.keyword("SPECIFIC");
      }
      SqlIdentifier id = function.getSqlIdentifier();
      if (id == null) {
        writer.keyword(operator.getName());
      } else {
        id.unparse(writer, 0, 0);
      }
    } else {
      writer.print(operator.getName());
    }
    if (call.operandCount() == 0) {
      switch (call.getOperator().getSyntax()) {
      case FUNCTION_ID:
        // For example, the "LOCALTIME" function appears as "LOCALTIME"
        // when it has 0 args, not "LOCALTIME()".
        return;
      case FUNCTION_STAR: // E.g. "COUNT(*)"
      case FUNCTION: // E.g. "RANK()"
        // fall through - dealt with below
      }
    }
    final SqlWriter.Frame frame =
        writer.startList(SqlWriter.FrameTypeEnum.FUN_CALL, "(", ")");
    final SqlLiteral quantifier = call.getFunctionQuantifier();
    if (quantifier != null) {
      quantifier.unparse(writer, 0, 0);
    }
    if (call.operandCount() == 0) {
      switch (call.getOperator().getSyntax()) {
      case FUNCTION_STAR:
        writer.sep("*");
      }
    }
    for (SqlNode operand : call.getOperandList()) {
      writer.sep(",");
      operand.unparse(writer, 0, 0);
    }
    writer.endList(frame);
  }

  public static void unparseBinarySyntax(
      SqlOperator operator,
      SqlCall call,
      SqlWriter writer,
      int leftPrec,
      int rightPrec) {
    assert call.operandCount() == 2;
    final SqlWriter.Frame frame =
        writer.startList(
            (operator instanceof SqlSetOperator)
                ? SqlWriter.FrameTypeEnum.SETOP
                : SqlWriter.FrameTypeEnum.SIMPLE);
    call.operand(0).unparse(writer, leftPrec, operator.getLeftPrec());
    final boolean needsSpace = operator.needsSpace();
    writer.setNeedWhitespace(needsSpace);
    writer.sep(operator.getName());
    writer.setNeedWhitespace(needsSpace);
    call.operand(1).unparse(writer, operator.getRightPrec(), rightPrec);
    writer.endList(frame);
  }

  /**
   * Concatenates string literals.
   *
   * <p>This method takes an array of arguments, since pairwise concatenation
   * means too much string copying.
   *
   * @param lits an array of {@link SqlLiteral}, not empty, all of the same
   *             class
   * @return a new {@link SqlLiteral}, of that same class, whose value is the
   * string concatenation of the values of the literals
   * @throws ClassCastException             if the lits are not homogeneous.
   * @throws ArrayIndexOutOfBoundsException if lits is an empty array.
   */
  public static SqlLiteral concatenateLiterals(List<SqlLiteral> lits) {
    if (lits.size() == 1) {
      return lits.get(0); // nothing to do
    }
    return ((SqlAbstractStringLiteral) lits.get(0)).concat1(lits);
  }

  /**
   * Looks up a (possibly overloaded) routine based on name and argument
   * types.
   *
   * @param opTab    operator table to search
   * @param funcName name of function being invoked
   * @param argTypes argument types
   * @param argNames argument names, or null if call by position
   * @param category whether a function or a procedure. (If a procedure is
   *                 being invoked, the overload rules are simpler.)
   * @return matching routine, or null if none found
   *
   * @see Glossary#SQL99 SQL:1999 Part 2 Section 10.4
   */
  public static SqlOperator lookupRoutine(SqlOperatorTable opTab,
      SqlIdentifier funcName, List<RelDataType> argTypes,
      List<String> argNames, SqlFunctionCategory category,
      SqlSyntax syntax, SqlKind sqlKind) {
    Iterator<SqlOperator> list =
        lookupSubjectRoutines(
            opTab,
            funcName,
            argTypes,
            argNames,
            syntax,
            sqlKind,
            category);
    if (list.hasNext()) {
      // return first on schema path
      return list.next();
    }
    return null;
  }

  private static Iterator<SqlOperator> filterOperatorRoutinesByKind(
      Iterator<SqlOperator> routines, final SqlKind sqlKind) {
    return Iterators.filter(routines,
        new PredicateImpl<SqlOperator>() {
          public boolean test(SqlOperator input) {
            return input.getKind() == sqlKind;
          }
        });
  }

  /**
   * Looks up all subject routines matching the given name and argument types.
   *
   * @param opTab     operator table to search
   * @param funcName  name of function being invoked
   * @param argTypes  argument types
   * @param argNames  argument names, or null if call by position
   * @param sqlSyntax the SqlSyntax of the SqlOperator being looked up
   * @param sqlKind   the SqlKind of the SqlOperator being looked up
   * @param category category of routine to look up
   * @return list of matching routines
   * @see Glossary#SQL99 SQL:1999 Part 2 Section 10.4
   */
  public static Iterator<SqlOperator> lookupSubjectRoutines(
      SqlOperatorTable opTab,
      SqlIdentifier funcName,
      List<RelDataType> argTypes,
      List<String> argNames,
      SqlSyntax sqlSyntax,
      SqlKind sqlKind,
      SqlFunctionCategory category) {
    // start with all routines matching by name
    Iterator<SqlOperator> routines =
        lookupSubjectRoutinesByName(opTab, funcName, sqlSyntax, category);

    // first pass:  eliminate routines which don't accept the given
    // number of arguments
    routines = filterRoutinesByParameterCount(routines, argTypes);

    // NOTE: according to SQL99, procedures are NOT overloaded on type,
    // only on number of arguments.
    if (category == SqlFunctionCategory.USER_DEFINED_PROCEDURE) {
      return routines;
    }

    // second pass:  eliminate routines which don't accept the given
    // argument types
    routines = filterRoutinesByParameterType(sqlSyntax, routines, argTypes, argNames);

    // see if we can stop now; this is necessary for the case
    // of builtin functions where we don't have param type info
    final List<SqlOperator> list = Lists.newArrayList(routines);
    routines = list.iterator();
    if (list.size() < 2) {
      return routines;
    }

    // third pass:  for each parameter from left to right, eliminate
    // all routines except those with the best precedence match for
    // the given arguments
    routines = filterRoutinesByTypePrecedence(sqlSyntax, routines, argTypes);

    // fourth pass: eliminate routines which do not have the same
    // SqlKind as requested
    return filterOperatorRoutinesByKind(routines, sqlKind);
  }

  /**
   * Determines whether there is a routine matching the given name and number
   * of arguments.
   *
   * @param opTab    operator table to search
   * @param funcName name of function being invoked
   * @param argTypes argument types
   * @param category category of routine to look up
   * @return true if match found
   */
  public static boolean matchRoutinesByParameterCount(
      SqlOperatorTable opTab,
      SqlIdentifier funcName,
      List<RelDataType> argTypes,
      SqlFunctionCategory category) {
    // start with all routines matching by name
    Iterator<SqlOperator> routines =
        lookupSubjectRoutinesByName(opTab, funcName, SqlSyntax.FUNCTION, category);

    // first pass:  eliminate routines which don't accept the given
    // number of arguments
    routines = filterRoutinesByParameterCount(routines, argTypes);

    return routines.hasNext();
  }

  private static Iterator<SqlOperator> lookupSubjectRoutinesByName(
      SqlOperatorTable opTab,
      SqlIdentifier funcName,
      final SqlSyntax syntax,
      SqlFunctionCategory category) {
    final List<SqlOperator> sqlOperators = new ArrayList<>();
    opTab.lookupOperatorOverloads(funcName, category, syntax, sqlOperators);
    switch (syntax) {
    case FUNCTION:
      return Iterators.filter(sqlOperators.iterator(),
          Predicates.instanceOf(SqlFunction.class));
    default:
      return Iterators.filter(sqlOperators.iterator(),
          new PredicateImpl<SqlOperator>() {
            public boolean test(SqlOperator operator) {
              return operator.getSyntax() == syntax;
            }
          });
    }
  }

  private static Iterator<SqlOperator> filterRoutinesByParameterCount(
      Iterator<SqlOperator> routines,
      final List<RelDataType> argTypes) {
    return Iterators.filter(routines,
        new PredicateImpl<SqlOperator>() {
          public boolean test(SqlOperator operator) {
            SqlOperandCountRange od = operator.getOperandCountRange();
            return od.isValidCount(argTypes.size());
          }
        });
  }

  /**
   * @see Glossary#SQL99 SQL:1999 Part 2 Section 10.4 Syntax Rule 6.b.iii.2.B
   */
  private static Iterator<SqlOperator> filterRoutinesByParameterType(
      SqlSyntax syntax,
      final Iterator<SqlOperator> routines,
      final List<RelDataType> argTypes, final List<String> argNames) {
    if (syntax != SqlSyntax.FUNCTION) {
      return routines;
    }

    //noinspection unchecked
    return (Iterator) Iterators.filter(
        Iterators.filter(routines, SqlFunction.class),
        new PredicateImpl<SqlFunction>() {
          public boolean test(SqlFunction function) {
            List<RelDataType> paramTypes = function.getParamTypes();
            if (paramTypes == null) {
              // no parameter information for builtins; keep for now
              return true;
            }
            final List<RelDataType> permutedArgTypes;
            if (argNames != null) {
              // Arguments passed by name. Make sure that the function has
              // parameters of all of these names.
              final Map<Integer, Integer> map = new HashMap<>();
              for (Ord<String> argName : Ord.zip(argNames)) {
                final int i = function.getParamNames().indexOf(argName.e);
                if (i < 0) {
                  return false;
                }
                map.put(i, argName.i);
              }
              permutedArgTypes = Functions.generate(paramTypes.size(),
                  new Function1<Integer, RelDataType>() {
                    public RelDataType apply(Integer a0) {
                      if (map.containsKey(a0)) {
                        return argTypes.get(map.get(a0));
                      } else {
                        return null;
                      }
                    }
                  });
            } else {
              permutedArgTypes = Lists.newArrayList(argTypes);
              while (permutedArgTypes.size() < argTypes.size()) {
                paramTypes.add(null);
              }
            }
            for (Pair<RelDataType, RelDataType> p
                : Pair.zip(paramTypes, permutedArgTypes)) {
              final RelDataType argType = p.right;
              final RelDataType paramType = p.left;
              if (argType != null
                  && !SqlTypeUtil.canCastFrom(paramType, argType, false)) {
                return false;
              }
            }
            return true;
          }
        });
  }

  /**
   * @see Glossary#SQL99 SQL:1999 Part 2 Section 9.4
   */
  private static Iterator<SqlOperator> filterRoutinesByTypePrecedence(
      SqlSyntax sqlSyntax,
      Iterator<SqlOperator> routines,
      List<RelDataType> argTypes) {
    if (sqlSyntax != SqlSyntax.FUNCTION) {
      return routines;
    }

    List<SqlFunction> sqlFunctions =
        Lists.newArrayList(Iterators.filter(routines, SqlFunction.class));

    for (final Ord<RelDataType> argType : Ord.zip(argTypes)) {
      final RelDataTypePrecedenceList precList =
          argType.e.getPrecedenceList();
      final RelDataType bestMatch = bestMatch(sqlFunctions, argType.i, precList);
      if (bestMatch != null) {
        sqlFunctions =
            Lists.newArrayList(
                Iterables.filter(sqlFunctions,
                    new PredicateImpl<SqlFunction>() {
                      public boolean test(SqlFunction function) {
                        final List<RelDataType> paramTypes = function.getParamTypes();
                        if (paramTypes == null) {
                          return false;
                        }
                        final RelDataType paramType = paramTypes.get(argType.i);
                        return precList.compareTypePrecedence(paramType, bestMatch) >= 0;
                      }
                    }));
      }
    }
    //noinspection unchecked
    return (Iterator) sqlFunctions.iterator();
  }

  private static RelDataType bestMatch(List<SqlFunction> sqlFunctions, int i,
      RelDataTypePrecedenceList precList) {
    RelDataType bestMatch = null;
    for (SqlFunction function : sqlFunctions) {
      List<RelDataType> paramTypes = function.getParamTypes();
      if (paramTypes == null) {
        continue;
      }
      final RelDataType paramType = paramTypes.get(i);
      if (bestMatch == null) {
        bestMatch = paramType;
      } else {
        int c =
            precList.compareTypePrecedence(
                bestMatch,
                paramType);
        if (c < 0) {
          bestMatch = paramType;
        }
      }
    }
    return bestMatch;
  }

  /**
   * Returns the <code>i</code>th select-list item of a query.
   */
  public static SqlNode getSelectListItem(SqlNode query, int i) {
    switch (query.getKind()) {
    case SELECT:
      SqlSelect select = (SqlSelect) query;
      final SqlNode from = stripAs(select.getFrom());
      if (from.getKind() == SqlKind.VALUES) {
        // They wrote "VALUES (x, y)", but the validator has
        // converted this into "SELECT * FROM VALUES (x, y)".
        return getSelectListItem(from, i);
      }
      final SqlNodeList fields = select.getSelectList();

      // Range check the index to avoid index out of range.  This
      // could be expanded to actually check to see if the select
      // list is a "*"
      if (i >= fields.size()) {
        i = 0;
      }
      return fields.get(i);

    case VALUES:
      SqlCall call = (SqlCall) query;
      assert call.operandCount() > 0
          : "VALUES must have at least one operand";
      final SqlCall row = call.operand(0);
      assert row.operandCount() > i : "VALUES has too few columns";
      return row.operand(i);

    default:
      // Unexpected type of query.
      throw Util.needToImplement(query);
    }
  }

  /**
   * If an identifier is a legitimate call to a function which has no
   * arguments and requires no parentheses (for example "CURRENT_USER"),
   * returns a call to that function, otherwise returns null.
   */
  public static SqlCall makeCall(
      SqlOperatorTable opTab,
      SqlIdentifier id) {
    if (id.names.size() == 1) {
      final List<SqlOperator> list = Lists.newArrayList();
      opTab.lookupOperatorOverloads(id, null, SqlSyntax.FUNCTION, list);
      for (SqlOperator operator : list) {
        if (operator.getSyntax() == SqlSyntax.FUNCTION_ID) {
          // Even though this looks like an identifier, it is a
          // actually a call to a function. Construct a fake
          // call to this function, so we can use the regular
          // operator validation.
          return new SqlBasicCall(
              operator,
              SqlNode.EMPTY_ARRAY,
              id.getParserPosition(),
              true,
              null);
        }
      }
    }
    return null;
  }

  public static String deriveAliasFromOrdinal(int ordinal) {
    // Use a '$' so that queries can't easily reference the
    // generated name.
    return "EXPR$" + ordinal;
  }

  /**
   * Constructs an operator signature from a type list.
   *
   * @param op       operator
   * @param typeList list of types to use for operands. Types may be
   *                 represented as {@link String}, {@link SqlTypeFamily}, or
   *                 any object with a valid {@link Object#toString()} method.
   * @return constructed signature
   */
  public static String getOperatorSignature(SqlOperator op, List<?> typeList) {
    return getAliasedSignature(op, op.getName(), typeList);
  }

  /**
   * Constructs an operator signature from a type list, substituting an alias
   * for the operator name.
   *
   * @param op       operator
   * @param opName   name to use for operator
   * @param typeList list of {@link SqlTypeName} or {@link String} to use for
   *                 operands
   * @return constructed signature
   */
  public static String getAliasedSignature(
      SqlOperator op,
      String opName,
      List<?> typeList) {
    StringBuilder ret = new StringBuilder();
    String template = op.getSignatureTemplate(typeList.size());
    if (null == template) {
      ret.append("'");
      ret.append(opName);
      ret.append("(");
      for (int i = 0; i < typeList.size(); i++) {
        if (i > 0) {
          ret.append(", ");
        }
        final String t = typeList.get(i).toString().toUpperCase(Locale.ROOT);
        ret.append("<").append(t).append(">");
      }
      ret.append(")'");
    } else {
      Object[] values = new Object[typeList.size() + 1];
      values[0] = opName;
      ret.append("'");
      for (int i = 0; i < typeList.size(); i++) {
        final String t = typeList.get(i).toString().toUpperCase(Locale.ROOT);
        values[i + 1] = "<" + t + ">";
      }
      ret.append(new MessageFormat(template, Locale.ROOT).format(values));
      ret.append("'");
      assert (typeList.size() + 1) == values.length;
    }

    return ret.toString();
  }

  /**
   * Wraps an exception with context.
   */
  public static CalciteException newContextException(
      final SqlParserPos pos,
      Resources.ExInst<?> e,
      String inputText) {
    CalciteContextException ex = newContextException(pos, e);
    ex.setOriginalStatement(inputText);
    return ex;
  }

  /**
   * Wraps an exception with context.
   */
  public static CalciteContextException newContextException(
      final SqlParserPos pos,
      Resources.ExInst<?> e) {
    int line = pos.getLineNum();
    int col = pos.getColumnNum();
    int endLine = pos.getEndLineNum();
    int endCol = pos.getEndColumnNum();
    return newContextException(line, col, endLine, endCol, e);
  }

  /**
   * Wraps an exception with context.
   */
  public static CalciteContextException newContextException(
      int line,
      int col,
      int endLine,
      int endCol,
      Resources.ExInst<?> e) {
    CalciteContextException contextExcn =
        (line == endLine && col == endCol
            ? RESOURCE.validatorContextPoint(line, col)
            : RESOURCE.validatorContext(line, col, endLine, endCol)).ex(e.ex());
    contextExcn.setPosition(line, col, endLine, endCol);
    return contextExcn;
  }

  /**
   * Returns whether a {@link SqlNode node} is a {@link SqlCall call} to a
   * given {@link SqlOperator operator}.
   */
  public static boolean isCallTo(SqlNode node, SqlOperator operator) {
    return (node instanceof SqlCall)
        && (((SqlCall) node).getOperator() == operator);
  }

  /**
   * Creates the type of an {@link org.apache.calcite.util.NlsString}.
   *
   * <p>The type inherits the The NlsString's {@link Charset} and
   * {@link SqlCollation}, if they are set, otherwise it gets the system
   * defaults.
   *
   * @param typeFactory Type factory
   * @param str         String
   * @return Type, including collation and charset
   */
  public static RelDataType createNlsStringType(
      RelDataTypeFactory typeFactory,
      NlsString str) {
    Charset charset = str.getCharset();
    if (null == charset) {
      charset = typeFactory.getDefaultCharset();
    }
    SqlCollation collation = str.getCollation();
    if (null == collation) {
      collation = SqlCollation.COERCIBLE;
    }
    RelDataType type =
        typeFactory.createSqlType(
            SqlTypeName.CHAR,
            str.getValue().length());
    type =
        typeFactory.createTypeWithCharsetAndCollation(
            type,
            charset,
            collation);
    return type;
  }

  /**
   * Translates a character set name from a SQL-level name into a Java-level
   * name.
   *
   * @param name SQL-level name
   * @return Java-level name, or null if SQL-level name is unknown
   */
  public static String translateCharacterSetName(String name) {
    if (name.equals("LATIN1")) {
      return "ISO-8859-1";
    } else if (name.equals("UTF16")) {
      return ConversionUtil.NATIVE_UTF16_CHARSET_NAME;
    } else if (name.equals(ConversionUtil.NATIVE_UTF16_CHARSET_NAME)) {
      // no translation needed
      return name;
    } else if (name.equals("ISO-8859-1")) {
      // no translation needed
      return name;
    }
    return null;
  }

  /** If a node is "AS", returns the underlying expression; otherwise returns
   * the node. */
  public static SqlNode stripAs(SqlNode node) {
    if (node != null && node.getKind() == SqlKind.AS) {
      return ((SqlCall) node).operand(0);
    }
    return node;
  }

  /** Returns a list of ancestors of {@code predicate} within a given
   * {@code SqlNode} tree.
   *
   * <p>The first element of the list is {@code root}, and the last is
   * the node that matched {@code predicate}. Throws if no node matches.
   */
  public static ImmutableList<SqlNode> getAncestry(SqlNode root,
      Predicate<SqlNode> predicate, Predicate<SqlNode> postPredicate) {
    try {
      new Genealogist(predicate, postPredicate).visitChild(root);
      throw new AssertionError("not found: " + predicate + " in " + root);
    } catch (Util.FoundOne e) {
      //noinspection unchecked
      return (ImmutableList<SqlNode>) e.getNode();
    }
  }

  //~ Inner Classes ----------------------------------------------------------

  /**
   * Handles particular {@link DatabaseMetaData} methods; invocations of other
   * methods will fall through to the base class,
   * {@link org.apache.calcite.util.BarfingInvocationHandler}, which will throw
   * an error.
   */
  public static class DatabaseMetaDataInvocationHandler
      extends BarfingInvocationHandler {
    private final String databaseProductName;
    private final String identifierQuoteString;

    public DatabaseMetaDataInvocationHandler(
        String databaseProductName,
        String identifierQuoteString) {
      this.databaseProductName = databaseProductName;
      this.identifierQuoteString = identifierQuoteString;
    }

    public String getDatabaseProductName() throws SQLException {
      return databaseProductName;
    }

    public String getIdentifierQuoteString() throws SQLException {
      return identifierQuoteString;
    }
  }

  /** Walks over a {@link org.apache.calcite.sql.SqlNode} tree and returns the
   * ancestry stack when it finds a given node. */
  private static class Genealogist extends SqlBasicVisitor<Void> {
    private final List<SqlNode> ancestors = Lists.newArrayList();
    private final Predicate<SqlNode> predicate;
    private final Predicate<SqlNode> postPredicate;

    Genealogist(Predicate<SqlNode> predicate,
        Predicate<SqlNode> postPredicate) {
      this.predicate = predicate;
      this.postPredicate = postPredicate;
    }

    private Void check(SqlNode node) {
      preCheck(node);
      postCheck(node);
      return null;
    }

    private Void preCheck(SqlNode node) {
      if (predicate.apply(node)) {
        throw new Util.FoundOne(ImmutableList.copyOf(ancestors));
      }
      return null;
    }

    private Void postCheck(SqlNode node) {
      if (postPredicate.apply(node)) {
        throw new Util.FoundOne(ImmutableList.copyOf(ancestors));
      }
      return null;
    }

    private void visitChild(SqlNode node) {
      if (node == null) {
        return;
      }
      ancestors.add(node);
      node.accept(this);
      ancestors.remove(ancestors.size() - 1);
    }

    @Override public Void visit(SqlIdentifier id) {
      return check(id);
    }

    @Override public Void visit(SqlCall call) {
      preCheck(call);
      for (SqlNode node : call.getOperandList()) {
        visitChild(node);
      }
      return postCheck(call);
    }

    @Override public Void visit(SqlIntervalQualifier intervalQualifier) {
      return check(intervalQualifier);
    }

    @Override public Void visit(SqlLiteral literal) {
      return check(literal);
    }

    @Override public Void visit(SqlNodeList nodeList) {
      preCheck(nodeList);
      for (SqlNode node : nodeList) {
        visitChild(node);
      }
      return postCheck(nodeList);
    }

    @Override public Void visit(SqlDynamicParam param) {
      return check(param);
    }

    @Override public Void visit(SqlDataTypeSpec type) {
      return check(type);
    }
  }
}

// End SqlUtil.java
