SqlJdbcFunctionCall.java

/*
 * 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.rel.type.RelDataType;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.fun.SqlTrimFunction;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;

import com.google.common.collect.ImmutableMap;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.Map;

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

import static java.util.Objects.requireNonNull;

/**
 * A <code>SqlJdbcFunctionCall</code> is a node of a parse tree which represents
 * a JDBC function call. A JDBC call is of the form <code>{fn NAME(arg0, arg1,
 * ...)}</code>.
 *
 * <p>See <a href="http://java.sun.com/products/jdbc/driverdevs.html">Sun's
 * documentation for writers of JDBC drivers</a>.*
 *
 * <table>
 * <caption>Supported JDBC functions</caption>
 * <tr>
 * <th>Function Name</th>
 * <th>Function Returns</th>
 * </tr>
 * <tr>
 * <td colspan="2"><br>
 *
 * <h2>NUMERIC FUNCTIONS</h2>
 * </td>
 * </tr>
 * <tr>
 * <td>ABS(number)</td>
 * <td>Absolute value of number</td>
 * </tr>
 * <tr>
 * <td>ACOS(float)</td>
 * <td>Arccosine, in radians, of float</td>
 * </tr>
 * <tr>
 * <td>ASIN(float)</td>
 * <td>Arcsine, in radians, of float</td>
 * </tr>
 * <tr>
 * <td>ATAN(float)</td>
 * <td>Arctangent, in radians, of float</td>
 * </tr>
 * <tr>
 * <td>ATAN2(float1, float2)</td>
 * <td>Arctangent, in radians, of float2 / float1</td>
 * </tr>
 * <tr>
 * <td>CBRT(number)</td>
 * <td>The cube root of number</td>
 * </tr>
 * <tr>
 * <td>CEILING(number)</td>
 * <td>Smallest integer &gt;= number</td>
 * </tr>
 * <tr>
 * <td>COS(float)</td>
 * <td>Cosine of float radians</td>
 * </tr>
 * <tr>
 * <td>COT(float)</td>
 * <td>Cotangent of float radians</td>
 * </tr>
 * <tr>
 * <td>DEGREES(number)</td>
 * <td>Degrees in number radians</td>
 * </tr>
 * <tr>
 * <td>EXP(float)</td>
 * <td>Exponential function of float</td>
 * </tr>
 * <tr>
 * <td>FLOOR(number)</td>
 * <td>Largest integer &lt;= number</td>
 * </tr>
 * <tr>
 * <td>LOG(float)</td>
 * <td>Base e logarithm of float</td>
 * </tr>
 * <tr>
 * <td>LOG10(float)</td>
 * <td>Base 10 logarithm of float</td>
 * </tr>
 * <tr>
 * <td>MOD(integer1, integer2)</td>
 * <td>Remainder for integer1 / integer2</td>
 * </tr>
 * <tr>
 * <td>PI()</td>
 * <td>The constant pi</td>
 * </tr>
 * <tr>
 * <td>POWER(number, power)</td>
 * <td>number raised to (integer) power</td>
 * </tr>
 * <tr>
 * <td>RADIANS(number)</td>
 * <td>Radians in number degrees</td>
 * </tr>
 * <tr>
 * <td>RAND(integer)</td>
 * <td>Random floating point for seed integer</td>
 * </tr>
 * <tr>
 * <td>ROUND(number, places)</td>
 * <td>number rounded to places places</td>
 * </tr>
 * <tr>
 * <td>SIGN(number)</td>
 * <td>-1 to indicate number is &lt; 0; 0 to indicate number is = 0; 1 to
 * indicate number is &gt; 0</td>
 * </tr>
 * <tr>
 * <td>SIN(float)</td>
 * <td>Sine of float radians</td>
 * </tr>
 * <tr>
 * <td>SQRT(float)</td>
 * <td>Square root of float</td>
 * </tr>
 * <tr>
 * <td>TAN(float)</td>
 * <td>Tangent of float radians</td>
 * </tr>
 * <tr>
 * <td>TRUNCATE(number, places)</td>
 * <td>number truncated to places places</td>
 * </tr>
 * <tr>
 * <td colspan="2"><br>
 *
 * <h2>STRING FUNCTIONS</h2>
 * </td>
 * </tr>
 * <tr>
 * <td>ASCII(string)</td>
 * <td>Integer representing the ASCII code value of the leftmost character in
 * string</td>
 * </tr>
 * <tr>
 * <td>CHAR(code)</td>
 * <td>Character with ASCII code value code, where code is between 0 and
 * 255</td>
 * </tr>
 * <tr>
 * <td>CONCAT(string1, string2)</td>
 * <td>Character string formed by appending string2 to string1; if a string is
 * null, the result is DBMS-dependent</td>
 * </tr>
 * <tr>
 * <td>DIFFERENCE(string1, string2)</td>
 * <td>Integer indicating the difference between the values returned by the
 * function SOUNDEX for string1 and string2</td>
 * </tr>
 * <tr>
 * <td>INSERT(string1, start, length, string2)</td>
 * <td>A character string formed by deleting length characters from string1
 * beginning at start, and inserting string2 into string1 at start</td>
 * </tr>
 * <tr>
 * <td>LCASE(string)</td>
 * <td>Converts all uppercase characters in string to lowercase</td>
 * </tr>
 * <tr>
 * <td>LEFT(string, count)</td>
 * <td>The count leftmost characters from string</td>
 * </tr>
 * <tr>
 * <td>LENGTH(string)</td>
 * <td>Number of characters in string, excluding trailing blanks</td>
 * </tr>
 * <tr>
 * <td>LOCATE(string1, string2[, start])</td>
 * <td>Position in string2 of the first occurrence of string1, searching from
 * the beginning of string2; if start is specified, the search begins from
 * position start. 0 is returned if string2 does not contain string1. Position 1
 * is the first character in string2.</td>
 * </tr>
 * <tr>
 * <td>LTRIM(string)</td>
 * <td>Characters of string with leading blank spaces removed</td>
 * </tr>
 * <tr>
 * <td>REPEAT(string, count)</td>
 * <td>A character string formed by repeating string count times</td>
 * </tr>
 * <tr>
 * <td>REPLACE(string1, string2, string3)</td>
 * <td>Replaces all occurrences of string2 in string1 with string3</td>
 * </tr>
 * <tr>
 * <td>RIGHT(string, count)</td>
 * <td>The count rightmost characters in string</td>
 * </tr>
 * <tr>
 * <td>RTRIM(string)</td>
 * <td>The characters of string with no trailing blanks</td>
 * </tr>
 * <tr>
 * <td>REVERSE(string)</td>
 * <td>The string with the order of the characters reversed</td>
 * </tr>
 * <tr>
 * <td>SOUNDEX(string)</td>
 * <td>A character string, which is data source-dependent, representing the
 * sound of the words in string; this could be a four-digit SOUNDEX code, a
 * phonetic representation of each word, etc.</td>
 * </tr>
 * <tr>
 * <td>SPACE(count)</td>
 * <td>A character string consisting of count spaces</td>
 * </tr>
 * <tr>
 * <td>SUBSTRING(string, start, length)</td>
 * <td>A character string formed by extracting length characters from string
 * beginning at start</td>
 * </tr>
 * <tr>
 * <td>UCASE(string)</td>
 * <td>Converts all lowercase characters in string to uppercase</td>
 * </tr>
 * <tr>
 * <td colspan="2"><br>
 *
 * <h2>TIME and DATE FUNCTIONS</h2>
 * </td>
 * </tr>
 * <tr>
 * <td>CURDATE()</td>
 * <td>The current date as a date value</td>
 * </tr>
 * <tr>
 * <td>CURTIME()</td>
 * <td>The current local time as a time value</td>
 * </tr>
 * <tr>
 * <td>DAYNAME(date)</td>
 * <td>A character string representing the day component of date; the name for
 * the day is specific to the data source</td>
 * </tr>
 * <tr>
 * <td>DAYOFMONTH(date)</td>
 * <td>An integer from 1 to 31 representing the day of the month in date</td>
 * </tr>
 * <tr>
 * <td>DAYOFWEEK(date)</td>
 * <td>An integer from 1 to 7 representing the day of the week in date; 1
 * represents Sunday</td>
 * </tr>
 * <tr>
 * <td>DAYOFYEAR(date)</td>
 * <td>An integer from 1 to 366 representing the day of the year in date</td>
 * </tr>
 * <tr>
 * <td>HOUR(time)</td>
 * <td>An integer from 0 to 23 representing the hour component of time</td>
 * </tr>
 * <tr>
 * <td>MINUTE(time)</td>
 * <td>An integer from 0 to 59 representing the minute component of time</td>
 * </tr>
 * <tr>
 * <td>MONTH(date)</td>
 * <td>An integer from 1 to 12 representing the month component of date</td>
 * </tr>
 * <tr>
 * <td>MONTHNAME(date)</td>
 * <td>A character string representing the month component of date; the name for
 * the month is specific to the data source</td>
 * </tr>
 * <tr>
 * <td>NOW()</td>
 * <td>A timestamp value representing the current date and time</td>
 * </tr>
 * <tr>
 * <td>QUARTER(date)</td>
 * <td>An integer from 1 to 4 representing the quarter in date; 1 represents
 * January 1 through March 31</td>
 * </tr>
 * <tr>
 * <td>SECOND(time)</td>
 * <td>An integer from 0 to 59 representing the second component of time</td>
 * </tr>
 * <tr>
 * <td>TIMESTAMPADD(interval,count, timestamp)</td>
 * <td>A timestamp calculated by adding count number of interval(s) to
 * timestamp; interval may be one of the following: SQL_TSI_FRAC_SECOND,
 * SQL_TSI_SECOND, SQL_TSI_MINUTE, SQL_TSI_HOUR, SQL_TSI_DAY, SQL_TSI_WEEK,
 * SQL_TSI_MONTH, SQL_TSI_QUARTER, or SQL_TSI_YEAR</td>
 * </tr>
 * <tr>
 * <td>TIMESTAMPDIFF(interval,timestamp1, timestamp2)</td>
 * <td>An integer representing the number of interval(s) by which timestamp2 is
 * greater than timestamp1; interval may be one of the following:
 * SQL_TSI_FRAC_SECOND, SQL_TSI_SECOND, SQL_TSI_MINUTE, SQL_TSI_HOUR,
 * SQL_TSI_DAY, SQL_TSI_WEEK, SQL_TSI_MONTH, SQL_TSI_QUARTER, or
 * SQL_TSI_YEAR</td>
 * </tr>
 * <tr>
 * <td>WEEK(date)</td>
 * <td>An integer from 1 to 53 representing the week of the year in date</td>
 * </tr>
 * <tr>
 * <td>YEAR(date)</td>
 * <td>An integer representing the year component of date</td>
 * </tr>
 * <tr>
 * <td colspan="2"><br>
 *
 * <h2>SYSTEM FUNCTIONS</h2>
 * </td>
 * </tr>
 * <tr>
 * <td>DATABASE()</td>
 * <td>Name of the database</td>
 * </tr>
 * <tr>
 * <td>IFNULL(expression, value)</td>
 * <td>value if expression is null; expression if expression is not null</td>
 * </tr>
 * <tr>
 * <td>USER()</td>
 * <td>User name in the DBMS
 *
 * <tr>
 * <td colspan="2"><br>
 *
 * <h2>CONVERSION FUNCTIONS</h2>
 * </td>
 * </tr>
 * <tr>
 * <td>CONVERT(value, SQLtype)</td>
 * <td>value converted to SQLtype where SQLtype may be one of the following SQL
 * types: BIGINT, BINARY, BIT, CHAR, DATE, DECIMAL, DOUBLE, FLOAT, INTEGER,
 * LONGVARBINARY, LONGVARCHAR, REAL, SMALLINT, TIME, TIMESTAMP, TINYINT,
 * VARBINARY, or VARCHAR</td>
 * </tr>
 * </table>
 */
public class SqlJdbcFunctionCall extends SqlFunction {
  //~ Static fields/initializers ---------------------------------------------

  /** List of all numeric function names defined by JDBC. */
  private static final String NUMERIC_FUNCTIONS =
      constructFuncList("ABS", "ACOS", "ASIN", "ATAN", "ATAN2",
          "CBRT", "CEILING", "COS", "COT",
          "DEGREES", "EXP", "FLOOR", "LOG", "LOG10", "MOD", "PI",
          "POWER", "RADIANS", "RAND", "ROUND", "SIGN", "SIN", "SQRT",
          "TAN", "TRUNCATE");

  /** List of all string function names defined by JDBC. */
  private static final String STRING_FUNCTIONS =
      constructFuncList("ASCII", "CHAR", "CONCAT",
          "DIFFERENCE", "INSERT", "LCASE",
          "LEFT", "LENGTH", "LOCATE", "LTRIM", "REPEAT", "REPLACE",
          "RIGHT", "RTRIM", "SOUNDEX", "SPACE", "SUBSTRING", "UCASE");

  /** List of all time/date function names defined by JDBC. */
  private static final String TIME_DATE_FUNCTIONS =
      constructFuncList("CONVERT_TIMEZONE", "CURDATE", "CURTIME",
          "DAYNAME", "DAYOFMONTH", "DAYOFWEEK",
          "DAYOFYEAR", "HOUR", "MINUTE", "MONTH", "MONTHNAME", "NOW",
          "QUARTER", "SECOND", "TIMESTAMPADD", "TIMESTAMPDIFF",
          "TO_DATE", "TO_TIMESTAMP", "WEEK", "YEAR");

  /** List of all system function names defined by JDBC. */
  private static final String SYSTEM_FUNCTIONS =
      constructFuncList("CONVERT", "DATABASE", "IFNULL", "USER");

  //~ Instance fields --------------------------------------------------------

  private final String jdbcName;
  private final @Nullable MakeCall lookupMakeCallObj;
  private @Nullable SqlCall lookupCall;

  private @Nullable SqlNode @Nullable [] thisOperands;

  //~ Constructors -----------------------------------------------------------

  public SqlJdbcFunctionCall(String name) {
    super(
        "{fn " + name + "}",
        SqlKind.JDBC_FN,
        null,
        null,
        OperandTypes.VARIADIC,
        SqlFunctionCategory.SYSTEM);
    jdbcName = name;
    lookupMakeCallObj = JdbcToInternalLookupTable.INSTANCE.lookup(name);
    lookupCall = null;
  }

  //~ Methods ----------------------------------------------------------------

  private static String constructFuncList(String... functionNames) {
    StringBuilder sb = new StringBuilder();
    int n = 0;
    for (String funcName : functionNames) {
      if (JdbcToInternalLookupTable.INSTANCE.lookup(funcName) == null) {
        continue;
      }
      if (n++ > 0) {
        sb.append(",");
      }
      sb.append(funcName);
    }
    return sb.toString();
  }

  @Override public SqlCall createCall(
      @Nullable SqlLiteral functionQualifier,
      SqlParserPos pos,
      @Nullable SqlNode... operands) {
    thisOperands = operands;
    return super.createCall(functionQualifier, pos, operands);
  }

  @Override public SqlNode rewriteCall(SqlValidator validator,
      SqlCall call) {
    if (null == lookupMakeCallObj) {
      throw validator.newValidationError(call,
          RESOURCE.functionUndefined(getName()));
    }
    return lookupMakeCallObj.getOperator().rewriteCall(validator, call);
  }

  public SqlCall getLookupCall() {
    if (null == lookupCall) {
      lookupCall =
          requireNonNull(lookupMakeCallObj, "lookupMakeCallObj")
              .createCall(SqlParserPos.ZERO, requireNonNull(thisOperands, "thisOperands"));
    }
    return lookupCall;
  }

  @Override public String getAllowedSignatures(String name) {
    return requireNonNull(lookupMakeCallObj, "lookupMakeCallObj")
        .getOperator().getAllowedSignatures(name);
  }

  @Override public RelDataType deriveType(
      SqlValidator validator,
      SqlValidatorScope scope,
      SqlCall call) {
    // Override SqlFunction.deriveType, because function-resolution is
    // not relevant to a JDBC function call.
    // REVIEW: jhyde, 2006/4/18: Should SqlJdbcFunctionCall even be a
    // subclass of SqlFunction?

    for (SqlNode operand : call.getOperandList()) {
      RelDataType nodeType = validator.deriveType(scope, operand);
      validator.setValidatedNodeType(operand, nodeType);
    }
    return validateOperands(validator, scope, call);
  }

  @Override public RelDataType inferReturnType(
      SqlOperatorBinding opBinding) {
    // only expected to come here if validator called this method
    SqlCallBinding callBinding = (SqlCallBinding) opBinding;

    if (null == lookupMakeCallObj) {
      throw callBinding.newValidationError(
          RESOURCE.functionUndefined(getName()));
    }

    final String message = lookupMakeCallObj.isValidArgCount(callBinding);
    if (message != null) {
      throw callBinding.newValidationError(
          RESOURCE.wrongNumberOfParam(getName(),
              requireNonNull(thisOperands, "thisOperands").length,
              message));
    }

    final SqlCall newCall = getLookupCall();
    final SqlCallBinding newBinding =
        new SqlCallBinding(callBinding.getValidator(), callBinding.getScope(),
            newCall);

    final SqlOperator operator = lookupMakeCallObj.getOperator();
    if (!operator.checkOperandTypes(newBinding, false)) {
      throw callBinding.newValidationSignatureError();
    }

    return operator.validateOperands(callBinding.getValidator(),
        callBinding.getScope(), newCall);
  }

  @Override public void unparse(
      SqlWriter writer,
      SqlCall call,
      int leftPrec,
      int rightPrec) {
    writer.print("{fn ");
    writer.print(jdbcName);
    final SqlWriter.Frame frame = writer.startList("(", ")");
    for (SqlNode operand : call.getOperandList()) {
      writer.sep(",");
      operand.unparse(writer, leftPrec, rightPrec);
    }
    writer.endList(frame);
    writer.print("}");
  }

  /**
   * As {@link java.sql.DatabaseMetaData#getNumericFunctions}.
   */
  public static String getNumericFunctions() {
    return NUMERIC_FUNCTIONS;
  }

  /**
   * As {@link java.sql.DatabaseMetaData#getStringFunctions}.
   */
  public static String getStringFunctions() {
    return STRING_FUNCTIONS;
  }

  /**
   * As {@link java.sql.DatabaseMetaData#getTimeDateFunctions}.
   */
  public static String getTimeDateFunctions() {
    return TIME_DATE_FUNCTIONS;
  }

  /**
   * As {@link java.sql.DatabaseMetaData#getSystemFunctions}.
   */
  public static String getSystemFunctions() {
    return SYSTEM_FUNCTIONS;
  }

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

  /** Converts a call to a JDBC function to a call to a regular function. */
  private interface MakeCall {
    /**
     * Creates and return a {@link SqlCall}. If the MakeCall strategy object
     * was created with a reordering specified the call will be created with
     * the operands reordered, otherwise no change of ordering is applied
     *
     * @param operands Operands
     */
    SqlCall createCall(SqlParserPos pos, @Nullable SqlNode... operands);

    SqlOperator getOperator();

    @Nullable String isValidArgCount(SqlCallBinding binding);
  }

  /** Converter that calls a built-in function with the same arguments. */
  public static class SimpleMakeCall implements SqlJdbcFunctionCall.MakeCall {
    final SqlOperator operator;

    public SimpleMakeCall(SqlOperator operator) {
      this.operator = operator;
    }

    @Override public SqlOperator getOperator() {
      return operator;
    }

    @Override public SqlCall createCall(SqlParserPos pos, @Nullable SqlNode... operands) {
      return operator.createCall(pos, operands);
    }

    @Override public @Nullable String isValidArgCount(SqlCallBinding binding) {
      return null; // any number of arguments is valid
    }
  }

  /** Implementation of {@link MakeCall} that can re-order or ignore operands. */
  private static class PermutingMakeCall extends SimpleMakeCall {
    final int[] order;

    /**
     * Creates a MakeCall strategy object with reordering of operands.
     *
     * <p>The reordering is specified by an int array where the value of
     * element at position <code>i</code> indicates to which element in a
     * new SqlNode[] array the operand goes.
     *
     * @param operator Operator
     * @param order Order
     */
    PermutingMakeCall(SqlOperator operator, int[] order) {
      super(operator);
      this.order = requireNonNull(order, "order");
    }

    @Override public SqlCall createCall(SqlParserPos pos,
        @Nullable SqlNode... operands) {
      return super.createCall(pos, reorder(operands));
    }

    @Override public @Nullable String isValidArgCount(SqlCallBinding binding) {
      if (order.length == binding.getOperandCount()) {
        return null; // operand count is valid
      } else {
        return getArgCountMismatchMsg(order.length);
      }
    }

    private static String getArgCountMismatchMsg(int... possible) {
      StringBuilder ret = new StringBuilder();
      for (int i = 0; i < possible.length; i++) {
        if (i > 0) {
          ret.append(" or ");
        }
        ret.append(possible[i]);
      }
      ret.append(" parameter(s)");
      return ret.toString();
    }

    /**
     * Uses the data in {@link #order} to reorder a SqlNode[] array.
     *
     * @param operands Operands
     */
    protected @Nullable SqlNode[] reorder(@Nullable SqlNode[] operands) {
      assert operands.length == order.length;
      @Nullable SqlNode[] newOrder = new SqlNode[operands.length];
      for (int i = 0; i < operands.length; i++) {
        assert operands[i] != null;
        int joyDivision = order[i];
        assert newOrder[joyDivision] == null : "mapping is not 1:1";
        newOrder[joyDivision] = operands[i];
      }
      return newOrder;
    }
  }

  /**
   * Lookup table between JDBC functions and internal representation.
   */
  private static class JdbcToInternalLookupTable {
    /**
     * The {@link org.apache.calcite.util.Glossary#SINGLETON_PATTERN singleton}
     * instance.
     */
    static final JdbcToInternalLookupTable INSTANCE =
        new JdbcToInternalLookupTable();

    private final Map<String, MakeCall> map;

    @SuppressWarnings("method.invocation.invalid")
    private JdbcToInternalLookupTable() {
      // A table of all functions can be found at
      // http://java.sun.com/products/jdbc/driverdevs.html
      // which is also provided in the javadoc for this class.
      // See also SqlOperatorTests.testJdbcFn, which contains the list.
      ImmutableMap.Builder<String, MakeCall> map = ImmutableMap.builder();
      map.put("ABS", simple(SqlStdOperatorTable.ABS));
      map.put("ACOS", simple(SqlStdOperatorTable.ACOS));
      map.put("ASIN", simple(SqlStdOperatorTable.ASIN));
      map.put("ATAN", simple(SqlStdOperatorTable.ATAN));
      map.put("ATAN2", simple(SqlStdOperatorTable.ATAN2));
      map.put("CBRT", simple(SqlStdOperatorTable.CBRT));
      map.put("CEILING", simple(SqlStdOperatorTable.CEIL));
      map.put("COS", simple(SqlStdOperatorTable.COS));
      map.put("COT", simple(SqlStdOperatorTable.COT));
      map.put("DEGREES", simple(SqlStdOperatorTable.DEGREES));
      map.put("EXP", simple(SqlStdOperatorTable.EXP));
      map.put("FLOOR", simple(SqlStdOperatorTable.FLOOR));
      map.put("LOG", simple(SqlStdOperatorTable.LN));
      map.put("LOG10", simple(SqlStdOperatorTable.LOG10));
      map.put("MOD", simple(SqlStdOperatorTable.MOD));
      map.put("PI", simple(SqlStdOperatorTable.PI));
      map.put("POWER", simple(SqlStdOperatorTable.POWER));
      map.put("RADIANS", simple(SqlStdOperatorTable.RADIANS));
      map.put("RAND", simple(SqlStdOperatorTable.RAND));
      map.put("ROUND", simple(SqlStdOperatorTable.ROUND));
      map.put("SIGN", simple(SqlStdOperatorTable.SIGN));
      map.put("SIN", simple(SqlStdOperatorTable.SIN));
      map.put("SQRT", simple(SqlStdOperatorTable.SQRT));
      map.put("TAN", simple(SqlStdOperatorTable.TAN));
      map.put("TRUNCATE", simple(SqlStdOperatorTable.TRUNCATE));

      map.put("ASCII", simple(SqlStdOperatorTable.ASCII));
      map.put("CHAR", simple(SqlLibraryOperators.CHAR));
      map.put("CONCAT", simple(SqlStdOperatorTable.CONCAT));
      map.put("DIFFERENCE", simple(SqlLibraryOperators.DIFFERENCE));
      map.put("INSERT",
          new PermutingMakeCall(SqlStdOperatorTable.OVERLAY, new int[]{0, 2, 3, 1}));
      map.put("LCASE", simple(SqlStdOperatorTable.LOWER));
      map.put("LENGTH", simple(SqlStdOperatorTable.CHARACTER_LENGTH));
      map.put("LOCATE", simple(SqlStdOperatorTable.POSITION));
      map.put("LEFT", simple(SqlLibraryOperators.LEFT));
      map.put("LTRIM", trim(SqlTrimFunction.Flag.LEADING));
      map.put("REPEAT", simple(SqlLibraryOperators.REPEAT));
      map.put("REPLACE", simple(SqlStdOperatorTable.REPLACE));
      map.put("REVERSE", simple(SqlLibraryOperators.REVERSE));
      map.put("RIGHT", simple(SqlLibraryOperators.RIGHT));
      map.put("RTRIM", trim(SqlTrimFunction.Flag.TRAILING));
      map.put("SOUNDEX", simple(SqlLibraryOperators.SOUNDEX));
      map.put("SPACE", simple(SqlLibraryOperators.SPACE));
      map.put("SUBSTRING", simple(SqlStdOperatorTable.SUBSTRING));
      map.put("UCASE", simple(SqlStdOperatorTable.UPPER));

      map.put("YEAR", simple(SqlStdOperatorTable.YEAR));
      map.put("QUARTER", simple(SqlStdOperatorTable.QUARTER));
      map.put("MONTH", simple(SqlStdOperatorTable.MONTH));
      map.put("WEEK", simple(SqlStdOperatorTable.WEEK));
      map.put("DAYOFYEAR", simple(SqlStdOperatorTable.DAYOFYEAR));
      map.put("DAYOFMONTH", simple(SqlStdOperatorTable.DAYOFMONTH));
      map.put("DAYOFWEEK", simple(SqlStdOperatorTable.DAYOFWEEK));
      map.put("DAYNAME", simple(SqlLibraryOperators.DAYNAME));
      map.put("HOUR", simple(SqlStdOperatorTable.HOUR));
      map.put("MINUTE", simple(SqlStdOperatorTable.MINUTE));
      map.put("MONTHNAME", simple(SqlLibraryOperators.MONTHNAME));
      map.put("SECOND", simple(SqlStdOperatorTable.SECOND));

      map.put("CONVERT_TIMEZONE", simple(SqlLibraryOperators.CONVERT_TIMEZONE));
      map.put("CURDATE", simple(SqlStdOperatorTable.CURRENT_DATE));
      map.put("CURTIME", simple(SqlStdOperatorTable.LOCALTIME));
      map.put("NOW", simple(SqlStdOperatorTable.CURRENT_TIMESTAMP));
      map.put("TIMESTAMPADD", simple(SqlStdOperatorTable.TIMESTAMP_ADD));
      map.put("TIMESTAMPDIFF", simple(SqlStdOperatorTable.TIMESTAMP_DIFF));
      map.put("TO_CHAR", simple(SqlLibraryOperators.TO_CHAR));
      map.put("TO_DATE", simple(SqlLibraryOperators.TO_DATE));
      map.put("TO_TIMESTAMP", simple(SqlLibraryOperators.TO_TIMESTAMP));

      map.put("DATABASE", simple(SqlStdOperatorTable.CURRENT_CATALOG));
      map.put("IFNULL",
          new SimpleMakeCall(SqlStdOperatorTable.COALESCE) {
            @Override public SqlCall createCall(SqlParserPos pos,
                @Nullable SqlNode... operands) {
              assert 2 == operands.length;
              return super.createCall(pos, operands);
            }
          });
      map.put("USER", simple(SqlStdOperatorTable.CURRENT_USER));
      map.put("CONVERT",
          new SimpleMakeCall(SqlStdOperatorTable.CAST) {
            @Override public SqlCall createCall(SqlParserPos pos,
                @Nullable SqlNode... operands) {
              assert 2 == operands.length;
              SqlNode typeOperand = operands[1];
              assert typeOperand != null && typeOperand.getKind() == SqlKind.LITERAL
                  : "literal expected, got " + typeOperand;

              SqlJdbcDataTypeName jdbcType = ((SqlLiteral) typeOperand)
                  .getValueAs(SqlJdbcDataTypeName.class);

              return super.createCall(pos, operands[0], jdbcType.createDataType(typeOperand.pos));
            }
          });
      this.map = map.build();
    }

    private static MakeCall trim(SqlTrimFunction.Flag flag) {
      return new SimpleMakeCall(SqlStdOperatorTable.TRIM) {
        @Override public SqlCall createCall(SqlParserPos pos,
            @Nullable SqlNode... operands) {
          assert 1 == operands.length;
          return super.createCall(pos, flag.symbol(pos),
              SqlLiteral.createCharString(" ", SqlParserPos.ZERO),
              operands[0]);
        }
      };
    }

    private static MakeCall simple(SqlOperator operator) {
      return new SimpleMakeCall(operator);
    }

    /**
     * Tries to lookup a given function name JDBC to an internal
     * representation. Returns null if no function defined.
     */
    public @Nullable MakeCall lookup(String name) {
      return map.get(name);
    }
  }
}