/* Copyright (c) 2001-2018, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.IntKeyIntValueHashMap;
import org.hsqldb.lib.StringConverter;
import org.hsqldb.lib.StringUtil;
import org.hsqldb.map.BitMap;
import org.hsqldb.map.ValuePool;
import org.hsqldb.persist.Crypto;
import org.hsqldb.persist.HsqlDatabaseProperties;
import org.hsqldb.types.ArrayType;
import org.hsqldb.types.BinaryData;
import org.hsqldb.types.BinaryType;
import org.hsqldb.types.BlobData;
import org.hsqldb.types.CharacterType;
import org.hsqldb.types.ClobData;
import org.hsqldb.types.DTIType;
import org.hsqldb.types.DateTimeType;
import org.hsqldb.types.IntervalMonthData;
import org.hsqldb.types.IntervalSecondData;
import org.hsqldb.types.IntervalType;
import org.hsqldb.types.LobData;
import org.hsqldb.types.NumberType;
import org.hsqldb.types.TimeData;
import org.hsqldb.types.TimestampData;
import org.hsqldb.types.Type;
import org.hsqldb.types.Types;

/**
 * Implementation of HSQLDB functions that are not defined by the
 * SQL standard.<p>
 *
 * Some functions are translated into equivalent SQL Standard functions.
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.4.1
 * @since 1.9.0
 */
public class FunctionCustom extends FunctionSQL {

    public static final String[] openGroupNumericFunctions = {
        "ABS", "ACOS", "ASIN", "ATAN", "ATAN2", "BITAND", "BITOR", "BITXOR",
        "CEILING", "COS", "COT", "DEGREES", "EXP", "FLOOR", "LOG", "LOG10",
        "MOD", "PI", "POWER", "RADIANS", "RAND", "ROUND", "ROUNDMAGIC", "SIGN",
        "SIN", "SQRT", "TAN", "TRUNCATE"
    };
    public static final String[] openGroupStringFunctions = {
        "ASCII", "CHAR", "CONCAT", "DIFFERENCE", "HEXTORAW", "INSERT", "LCASE",
        "LEFT", "LENGTH", "LOCATE", "LTRIM", "RAWTOHEX", "REPEAT", "REPLACE",
        "RIGHT", "RTRIM", "SOUNDEX", "SPACE", "SUBSTR", "UCASE",
    };
    public static final String[] openGroupDateTimeFunctions = {
        "CURDATE", "CURTIME", "DATEDIFF", "DAYNAME", "DAYOFMONTH", "DAYOFWEEK",
        "DAYOFYEAR", "HOUR", "MINUTE", "MONTH", "MONTHNAME", "NOW", "QUARTER",
        "SECOND", "SECONDS_SINCE_MIDNIGHT", "TIMESTAMPADD", "TIMESTAMPDIFF",
        "TO_CHAR", "WEEK", "YEAR"
    };
    public static final String[] openGroupSystemFunctions = {
        "DATABASE", "IFNULL", "USER"
    };

    //
    private static final int FUNC_ACOS                     = 71;
    private static final int FUNC_ACTION_ID                = 72;
    private static final int FUNC_ADD_MONTHS               = 73;
    private static final int FUNC_ASCII                    = 74;
    private static final int FUNC_ASIN                     = 75;
    private static final int FUNC_ATAN                     = 76;
    private static final int FUNC_ATAN2                    = 77;
    private static final int FUNC_BITAND                   = 78;
    private static final int FUNC_BITANDNOT                = 79;
    private static final int FUNC_BITNOT                   = 80;
    private static final int FUNC_BITOR                    = 81;
    private static final int FUNC_BITXOR                   = 82;
    private static final int FUNC_CHAR                     = 83;
    private static final int FUNC_CONCAT                   = 84;
    private static final int FUNC_COS                      = 85;
    private static final int FUNC_COSH                     = 86;
    private static final int FUNC_COT                      = 87;
    private static final int FUNC_CRYPT_KEY                = 88;
    private static final int FUNC_DATABASE                 = 89;
    private static final int FUNC_DATABASE_ISOLATION_LEVEL = 90;
    private static final int FUNC_DATABASE_NAME            = 91;
    private static final int FUNC_DATABASE_TIMEZONE        = 92;
    private static final int FUNC_DATABASE_VERSION         = 93;
    private static final int FUNC_DATE_ADD                 = 94;
    private static final int FUNC_DATE_SUB                 = 95;
    private static final int FUNC_DATEADD                  = 96;
    private static final int FUNC_DATEDIFF                 = 97;
    private static final int FUNC_DAYS                     = 98;
    private static final int FUNC_DBTIMEZONE               = 99;
    private static final int FUNC_DEGREES                  = 100;
    private static final int FUNC_DIAGNOSTICS              = 101;
    private static final int FUNC_DIFFERENCE               = 102;
    private static final int FUNC_FROM_BASE64              = 103;
    private static final int FUNC_FROM_TZ                  = 104;
    private static final int FUNC_HEX                      = 105;
    private static final int FUNC_HEXTORAW                 = 106;
    private static final int FUNC_IDENTITY                 = 107;
    private static final int FUNC_INSTR                    = 108;
    private static final int FUNC_ISAUTOCOMMIT             = 109;
    private static final int FUNC_ISOLATION_LEVEL          = 110;
    private static final int FUNC_ISREADONLYDATABASE       = 111;
    private static final int FUNC_ISREADONLYDATABASEFILES  = 112;
    private static final int FUNC_ISREADONLYSESSION        = 113;
    private static final int FUNC_LAST_DAY                 = 114;
    private static final int FUNC_LEFT                     = 115;
    private static final int FUNC_LOAD_FILE                = 116;
    private static final int FUNC_LOB_ID                   = 117;
    private static final int FUNC_LOCATE                   = 118;
    private static final int FUNC_LOG10                    = 119;
    private static final int FUNC_LPAD                     = 120;
    private static final int FUNC_LTRIM                    = 121;
    private static final int FUNC_MONTHS_BETWEEN           = 122;
    private static final int FUNC_NEW_TIME                 = 123;
    private static final int FUNC_NEXT_DAY                 = 124;
    private static final int FUNC_NUMTODSINTERVAL          = 125;
    private static final int FUNC_NUMTOYMINTERVAL          = 126;
    private static final int FUNC_PI                       = 127;
    private static final int FUNC_POSITION_ARRAY           = 128;
    private static final int FUNC_RADIANS                  = 129;
    private static final int FUNC_RAND                     = 130;
    private static final int FUNC_RAWTOHEX                 = 131;
    private static final int FUNC_REGEXP_MATCHES           = 132;
    private static final int FUNC_REGEXP_REPLACE           = 133;
    private static final int FUNC_REGEXP_SUBSTRING         = 134;
    private static final int FUNC_REGEXP_SUBSTRING_ARRAY   = 135;
    private static final int FUNC_REPEAT                   = 136;
    private static final int FUNC_REPLACE                  = 137;
    private static final int FUNC_REVERSE                  = 138;
    private static final int FUNC_RIGHT                    = 139;
    private static final int FUNC_ROUND                    = 140;
    private static final int FUNC_ROUNDMAGIC               = 141;
    private static final int FUNC_RPAD                     = 142;
    private static final int FUNC_RTRIM                    = 143;
    private static final int FUNC_SECONDS_MIDNIGHT         = 144;
    private static final int FUNC_SEQUENCE_ARRAY           = 145;
    private static final int FUNC_SESSION_ID               = 146;
    private static final int FUNC_SESSION_ISOLATION_LEVEL  = 147;
    private static final int FUNC_SESSION_TIMEZONE         = 148;
    private static final int FUNC_SESSIONTIMEZONE          = 149;
    private static final int FUNC_SIGN                     = 150;
    private static final int FUNC_SIN                      = 151;
    private static final int FUNC_SINH                     = 152;
    private static final int FUNC_SOUNDEX                  = 153;
    private static final int FUNC_SORT_ARRAY               = 154;
    private static final int FUNC_SPACE                    = 155;
    private static final int FUNC_SUBSTR                   = 156;
    private static final int FUNC_SYS_EXTRACT_UTC          = 157;
    private static final int FUNC_SYSDATE                  = 158;
    private static final int FUNC_SYSTIMESTAMP             = 159;
    private static final int FUNC_TAN                      = 160;
    private static final int FUNC_TANH                     = 161;
    private static final int FUNC_TIMESTAMP                = 162;
    private static final int FUNC_TIMESTAMP_WITH_ZONE      = 163;
    private static final int FUNC_TIMESTAMPADD             = 164;
    private static final int FUNC_TIMESTAMPDIFF            = 165;
    private static final int FUNC_TIMEZONE                 = 166;
    private static final int FUNC_TO_BASE64                = 167;
    private static final int FUNC_TO_CHAR                  = 168;
    private static final int FUNC_TO_DATE                  = 169;
    private static final int FUNC_TO_DSINTERVAL            = 170;
    private static final int FUNC_TO_YMINTERVAL            = 171;
    private static final int FUNC_TO_NUMBER                = 172;
    private static final int FUNC_TO_TIMESTAMP             = 173;
    private static final int FUNC_TO_TIMESTAMP_TZ          = 174;
    private static final int FUNC_TRANSACTION_CONTROL      = 175;
    private static final int FUNC_TRANSACTION_ID           = 176;
    private static final int FUNC_TRANSACTION_SIZE         = 177;
    private static final int FUNC_TRANSLATE                = 178;
    private static final int FUNC_TRUNC                    = 179;
    private static final int FUNC_TRUNCATE                 = 180;
    private static final int FUNC_UNHEX                    = 181;
    private static final int FUNC_UUID                     = 182;
    private static final int FUNC_UNIX_TIMESTAMP           = 183;
    private static final int FUNC_UNIX_MILLIS              = 184;
    private static final int FUNC_DATEPART                 = 185;
    private static final int FUNC_DATENAME                 = 186;
    private static final int FUNC_NANVL                    = 187;
    private static final int FUNC_SQLCODE                  = 188;
    private static final int FUNC_SQLERRM                  = 189;

    //
    static final IntKeyIntValueHashMap customRegularFuncMap =
        new IntKeyIntValueHashMap();

    //J-
    static {
        nonDeterministicFuncSet.add(FUNC_ACTION_ID);
        nonDeterministicFuncSet.add(FUNC_CRYPT_KEY);
        nonDeterministicFuncSet.add(FUNC_DATABASE);
        nonDeterministicFuncSet.add(FUNC_DATABASE_ISOLATION_LEVEL);
        nonDeterministicFuncSet.add(FUNC_DATABASE_TIMEZONE);
        nonDeterministicFuncSet.add(FUNC_IDENTITY);
        nonDeterministicFuncSet.add(FUNC_ISAUTOCOMMIT);
        nonDeterministicFuncSet.add(FUNC_ISREADONLYSESSION);
        nonDeterministicFuncSet.add(FUNC_ISREADONLYDATABASE);
        nonDeterministicFuncSet.add(FUNC_ISREADONLYDATABASEFILES);
        nonDeterministicFuncSet.add(FUNC_ISOLATION_LEVEL);
        nonDeterministicFuncSet.add(FUNC_SESSION_ID);
        nonDeterministicFuncSet.add(FUNC_SESSION_ISOLATION_LEVEL);
        nonDeterministicFuncSet.add(FUNC_SESSION_TIMEZONE);
        nonDeterministicFuncSet.add(FUNC_SESSIONTIMEZONE);
        nonDeterministicFuncSet.add(FUNC_SYSDATE);
        nonDeterministicFuncSet.add(FUNC_SYSTIMESTAMP);
        nonDeterministicFuncSet.add(FUNC_TIMESTAMP);
        nonDeterministicFuncSet.add(FUNC_TIMEZONE);
        nonDeterministicFuncSet.add(FUNC_TRANSACTION_CONTROL);
        nonDeterministicFuncSet.add(FUNC_TRANSACTION_ID);
        nonDeterministicFuncSet.add(FUNC_TRANSACTION_SIZE);
        nonDeterministicFuncSet.add(FUNC_UUID);
        nonDeterministicFuncSet.add(FUNC_UNIX_TIMESTAMP);
        nonDeterministicFuncSet.add(FUNC_UNIX_MILLIS);

        //
        customRegularFuncMap.put(Tokens.ACOS, FUNC_ACOS);
        customRegularFuncMap.put(Tokens.ACTION_ID, FUNC_ACTION_ID);
        customRegularFuncMap.put(Tokens.ADD_MONTHS, FUNC_ADD_MONTHS);
        customRegularFuncMap.put(Tokens.ARRAY_SORT, FUNC_SORT_ARRAY);
        customRegularFuncMap.put(Tokens.ASCII, FUNC_ASCII);
        customRegularFuncMap.put(Tokens.ASIN, FUNC_ASIN);
        customRegularFuncMap.put(Tokens.ATAN, FUNC_ATAN);
        customRegularFuncMap.put(Tokens.ATAN2, FUNC_ATAN2);
        customRegularFuncMap.put(Tokens.BITAND, FUNC_BITAND);
        customRegularFuncMap.put(Tokens.BITANDNOT, FUNC_BITANDNOT);
        customRegularFuncMap.put(Tokens.BITLENGTH, FUNC_BIT_LENGTH);
        customRegularFuncMap.put(Tokens.BITNOT, FUNC_BITNOT);
        customRegularFuncMap.put(Tokens.BITOR, FUNC_BITOR);
        customRegularFuncMap.put(Tokens.BITXOR, FUNC_BITXOR);
        customRegularFuncMap.put(Tokens.CHAR, FUNC_CHAR);
        customRegularFuncMap.put(Tokens.CHR, FUNC_CHAR);
        customRegularFuncMap.put(Tokens.CONCAT_WORD, FUNC_CONCAT);
        customRegularFuncMap.put(Tokens.COS, FUNC_COS);
        customRegularFuncMap.put(Tokens.COSH, FUNC_COSH);
        customRegularFuncMap.put(Tokens.COT, FUNC_COT);
        customRegularFuncMap.put(Tokens.CRYPT_KEY, FUNC_CRYPT_KEY);
        customRegularFuncMap.put(Tokens.CURDATE, FUNC_CURRENT_DATE);
        customRegularFuncMap.put(Tokens.CURTIME, FUNC_LOCALTIME);
        customRegularFuncMap.put(Tokens.DATABASE, FUNC_DATABASE);
        customRegularFuncMap.put(Tokens.DATABASE_NAME, FUNC_DATABASE_NAME);
        customRegularFuncMap.put(Tokens.DATABASE_ISOLATION_LEVEL, FUNC_DATABASE_ISOLATION_LEVEL);
        customRegularFuncMap.put(Tokens.DATABASE_TIMEZONE, FUNC_DATABASE_TIMEZONE);
        customRegularFuncMap.put(Tokens.DATABASE_VERSION, FUNC_DATABASE_VERSION);
        customRegularFuncMap.put(Tokens.DATE_ADD, FUNC_DATE_ADD);
        customRegularFuncMap.put(Tokens.DATE_SUB, FUNC_DATE_SUB);
        customRegularFuncMap.put(Tokens.DATEADD, FUNC_DATEADD);
        customRegularFuncMap.put(Tokens.DATEDIFF, FUNC_DATEDIFF);
        customRegularFuncMap.put(Tokens.DATENAME, FUNC_DATENAME);
        customRegularFuncMap.put(Tokens.DATEPART, FUNC_DATEPART);
        customRegularFuncMap.put(Tokens.DAY, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.DAYNAME, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.DAYOFMONTH, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.DAYOFWEEK, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.DAYOFYEAR, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.DAYS, FUNC_DAYS);
        customRegularFuncMap.put(Tokens.DBTIMEZONE, FUNC_DBTIMEZONE);
        customRegularFuncMap.put(Tokens.DEGREES, FUNC_DEGREES);
        customRegularFuncMap.put(Tokens.DIAGNOSTICS, FUNC_DIAGNOSTICS);
        customRegularFuncMap.put(Tokens.DIFFERENCE, FUNC_DIFFERENCE);
        customRegularFuncMap.put(Tokens.EOMONTH, FUNC_LAST_DAY);
        customRegularFuncMap.put(Tokens.FROM_BASE64, FUNC_FROM_BASE64);
        customRegularFuncMap.put(Tokens.FROM_TZ, FUNC_FROM_TZ);
        customRegularFuncMap.put(Tokens.HEX, FUNC_HEX);
        customRegularFuncMap.put(Tokens.HEXTORAW, FUNC_HEXTORAW);
        customRegularFuncMap.put(Tokens.HOUR, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.IDENTITY, FUNC_IDENTITY);
        customRegularFuncMap.put(Tokens.INSERT, FUNC_OVERLAY_CHAR);
        customRegularFuncMap.put(Tokens.INSTR, FUNC_POSITION_CHAR);
        customRegularFuncMap.put(Tokens.IS_AUTOCOMMIT, FUNC_ISAUTOCOMMIT);
        customRegularFuncMap.put(Tokens.IS_READONLY_DATABASE, FUNC_ISREADONLYDATABASE);
        customRegularFuncMap.put(Tokens.IS_READONLY_DATABASE_FILES, FUNC_ISREADONLYDATABASEFILES);
        customRegularFuncMap.put(Tokens.IS_READONLY_SESSION, FUNC_ISREADONLYSESSION);
        customRegularFuncMap.put(Tokens.ISOLATION_LEVEL, FUNC_ISOLATION_LEVEL);
        customRegularFuncMap.put(Tokens.LAST_DAY, FUNC_LAST_DAY);
        customRegularFuncMap.put(Tokens.LCASE, FUNC_FOLD_LOWER);
        customRegularFuncMap.put(Tokens.LEFT, FUNC_LEFT);
        customRegularFuncMap.put(Tokens.LENGTH, FUNC_CHAR_LENGTH);
        customRegularFuncMap.put(Tokens.LOAD_FILE, FUNC_LOAD_FILE);
        customRegularFuncMap.put(Tokens.LOB_ID, FUNC_LOB_ID);
        customRegularFuncMap.put(Tokens.LOCATE, FUNC_POSITION_CHAR);
        customRegularFuncMap.put(Tokens.LOG, FUNC_LN);
        customRegularFuncMap.put(Tokens.LOG10, FUNC_LOG10);
        customRegularFuncMap.put(Tokens.LPAD, FUNC_LPAD);
        customRegularFuncMap.put(Tokens.LTRIM, FUNC_TRIM_CHAR);
        customRegularFuncMap.put(Tokens.MINUTE, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.MONTH, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.MONTHNAME, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.MONTHS_BETWEEN, FUNC_MONTHS_BETWEEN);
        customRegularFuncMap.put(Tokens.NANVL, FUNC_NANVL);
        customRegularFuncMap.put(Tokens.NEWID, FUNC_UUID);
        customRegularFuncMap.put(Tokens.NEW_TIME, FUNC_NEW_TIME);
        customRegularFuncMap.put(Tokens.NEXT_DAY, FUNC_NEXT_DAY);
        customRegularFuncMap.put(Tokens.NUMTODSINTERVAL, FUNC_NUMTODSINTERVAL);
        customRegularFuncMap.put(Tokens.NUMTOYMINTERVAL, FUNC_NUMTOYMINTERVAL);
        customRegularFuncMap.put(Tokens.OCTETLENGTH, FUNC_OCTET_LENGTH);
        customRegularFuncMap.put(Tokens.PI, FUNC_PI);
        customRegularFuncMap.put(Tokens.POSITION_ARRAY, FUNC_POSITION_ARRAY);
        customRegularFuncMap.put(Tokens.QUARTER, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.RADIANS, FUNC_RADIANS);
        customRegularFuncMap.put(Tokens.RAND, FUNC_RAND);
        customRegularFuncMap.put(Tokens.RAWTOHEX, FUNC_RAWTOHEX);
        customRegularFuncMap.put(Tokens.REGEXP_MATCHES, FUNC_REGEXP_MATCHES);
        customRegularFuncMap.put(Tokens.REGEXP_REPLACE, FUNC_REGEXP_REPLACE);
        customRegularFuncMap.put(Tokens.REGEXP_SUBSTRING, FUNC_REGEXP_SUBSTRING);
        customRegularFuncMap.put(Tokens.REGEXP_SUBSTRING_ARRAY, FUNC_REGEXP_SUBSTRING_ARRAY);
        customRegularFuncMap.put(Tokens.REPEAT, FUNC_REPEAT);
        customRegularFuncMap.put(Tokens.REPLACE, FUNC_REPLACE);
        customRegularFuncMap.put(Tokens.REVERSE, FUNC_REVERSE);
        customRegularFuncMap.put(Tokens.RIGHT, FUNC_RIGHT);
        customRegularFuncMap.put(Tokens.ROUND, FUNC_ROUND);
        customRegularFuncMap.put(Tokens.ROUNDMAGIC, FUNC_ROUNDMAGIC);
        customRegularFuncMap.put(Tokens.RPAD, FUNC_RPAD);
        customRegularFuncMap.put(Tokens.RTRIM, FUNC_TRIM_CHAR);
        customRegularFuncMap.put(Tokens.SECOND, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.SECONDS_MIDNIGHT, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.SEQUENCE_ARRAY, FUNC_SEQUENCE_ARRAY);
        customRegularFuncMap.put(Tokens.SESSION_ID, FUNC_SESSION_ID);
        customRegularFuncMap.put(Tokens.SESSION_ISOLATION_LEVEL, FUNC_SESSION_ISOLATION_LEVEL);
        customRegularFuncMap.put(Tokens.SESSION_TIMEZONE, FUNC_SESSION_TIMEZONE);
        customRegularFuncMap.put(Tokens.SESSIONTIMEZONE, FUNC_SESSIONTIMEZONE);
        customRegularFuncMap.put(Tokens.SIGN, FUNC_SIGN);
        customRegularFuncMap.put(Tokens.SIN, FUNC_SIN);
        customRegularFuncMap.put(Tokens.SINH, FUNC_SINH);
        customRegularFuncMap.put(Tokens.SORT_ARRAY, FUNC_SORT_ARRAY);
        customRegularFuncMap.put(Tokens.SOUNDEX, FUNC_SOUNDEX);
        customRegularFuncMap.put(Tokens.SPACE, FUNC_SPACE);
        customRegularFuncMap.put(Tokens.SUBSTR, FUNC_SUBSTRING_CHAR);
        customRegularFuncMap.put(Tokens.SYS_EXTRACT_UTC, FUNC_SYS_EXTRACT_UTC);
        customRegularFuncMap.put(Tokens.SYSDATE, FUNC_SYSDATE);
        customRegularFuncMap.put(Tokens.SYSTIMESTAMP, FUNC_SYSTIMESTAMP);
        customRegularFuncMap.put(Tokens.TAN, FUNC_TAN);
        customRegularFuncMap.put(Tokens.TANH, FUNC_TANH);
        customRegularFuncMap.put(Tokens.TIMESTAMP, FUNC_TIMESTAMP);
        customRegularFuncMap.put(Tokens.TIMESTAMP_WITH_ZONE, FUNC_TIMESTAMP_WITH_ZONE);
        customRegularFuncMap.put(Tokens.TIMESTAMPADD, FUNC_TIMESTAMPADD);
        customRegularFuncMap.put(Tokens.TIMESTAMPDIFF, FUNC_TIMESTAMPDIFF);
        customRegularFuncMap.put(Tokens.TIMEZONE, FUNC_TIMEZONE);
        customRegularFuncMap.put(Tokens.TO_BASE64, FUNC_TO_BASE64);
        customRegularFuncMap.put(Tokens.TO_CHAR, FUNC_TO_CHAR);
        customRegularFuncMap.put(Tokens.TO_DATE, FUNC_TO_DATE);
        customRegularFuncMap.put(Tokens.TO_DSINTERVAL, FUNC_TO_DSINTERVAL);
        customRegularFuncMap.put(Tokens.TO_YMINTERVAL, FUNC_TO_YMINTERVAL);
        customRegularFuncMap.put(Tokens.TO_NUMBER, FUNC_TO_NUMBER);
        customRegularFuncMap.put(Tokens.TO_TIMESTAMP, FUNC_TO_TIMESTAMP);
//      customRegularFuncMap.put(Tokens.TO_TIMESTAMP_TZ, FUNC_TO_TIMESTAMP_TZ);
        customRegularFuncMap.put(Tokens.TRANSACTION_CONTROL, FUNC_TRANSACTION_CONTROL);
        customRegularFuncMap.put(Tokens.TRANSACTION_ID, FUNC_TRANSACTION_ID);
        customRegularFuncMap.put(Tokens.TRANSACTION_SIZE, FUNC_TRANSACTION_SIZE);
        customRegularFuncMap.put(Tokens.TRANSLATE, FUNC_TRANSLATE);
        customRegularFuncMap.put(Tokens.TRUNC, FUNC_TRUNC);
        customRegularFuncMap.put(Tokens.TRUNCATE, FUNC_TRUNCATE);
        customRegularFuncMap.put(Tokens.UCASE, FUNC_FOLD_UPPER);
        customRegularFuncMap.put(Tokens.UNIX_MILLIS, FUNC_UNIX_MILLIS);
        customRegularFuncMap.put(Tokens.UNIX_TIMESTAMP, FUNC_UNIX_TIMESTAMP);
        customRegularFuncMap.put(Tokens.SYS_GUID, FUNC_UUID);
        customRegularFuncMap.put(Tokens.UNHEX, FUNC_UNHEX);
        customRegularFuncMap.put(Tokens.UUID, FUNC_UUID);
        customRegularFuncMap.put(Tokens.WEEK, FUNC_EXTRACT);
        customRegularFuncMap.put(Tokens.YEAR, FUNC_EXTRACT);
    }
    //J+
    static final IntKeyIntValueHashMap customValueFuncMap =
        new IntKeyIntValueHashMap();

    static {
        customValueFuncMap.put(Tokens.TODAY, FUNC_CURRENT_DATE);
        customValueFuncMap.put(Tokens.NOW, FUNC_LOCALTIMESTAMP);
    }

    private int                   extractSpec;
    private Pattern               pattern;
    private IntKeyIntValueHashMap charLookup;

    public static FunctionSQL newCustomFunction(Session session, String token,
            int tokenType) {

        int id = customRegularFuncMap.get(tokenType, -1);

        if (id == -1) {
            id = customValueFuncMap.get(tokenType, -1);
        }

        if (id == -1) {
            return null;
        }

        switch (tokenType) {

            case Tokens.BITLENGTH :
            case Tokens.LCASE :
            case Tokens.LENGTH :
            case Tokens.LOG :
            case Tokens.OCTETLENGTH :
            case Tokens.UCASE :
                return new FunctionSQL(id);

            case Tokens.CURDATE :
            case Tokens.CURTIME :
            case Tokens.TODAY :
            case Tokens.NOW : {
                FunctionSQL function = new FunctionSQL(id);

                function.parseList = optionalNoParamList;

                return function;
            }
            case Tokens.SUBSTR : {
                FunctionSQL function = new FunctionSQL(id);

                function.parseList = tripleParamList;

                return function;
            }
            case Tokens.DATENAME :
            case Tokens.DATEPART :
            case Tokens.EOMONTH :
            case Tokens.NEWID :
                if (!session.database.sqlSyntaxMss) {
                    return null;
                }
                break;

            case Tokens.DBTIMEZONE :
            case Tokens.SYS_GUID :
            case Tokens.SYSTIMESTAMP :
                if (!session.database.sqlSyntaxOra) {
                    return null;
                }
        }

        FunctionCustom function = new FunctionCustom(id);

        if (id == FUNC_TRIM_CHAR) {
            switch (tokenType) {

                case Tokens.LTRIM :
                    function.extractSpec = Tokens.LEADING;
                    break;

                case Tokens.RTRIM :
                    function.extractSpec = Tokens.TRAILING;
                    break;

                default :
            }
        }

        if (id == FUNC_EXTRACT) {
            switch (tokenType) {

                case Tokens.DAYNAME :
                    function.extractSpec = Tokens.DAY_NAME;
                    break;

                case Tokens.MONTHNAME :
                    function.extractSpec = Tokens.MONTH_NAME;
                    break;

                case Tokens.DAYOFMONTH :
                    function.extractSpec = Tokens.DAY_OF_MONTH;
                    break;

                case Tokens.DAYOFWEEK :
                    function.extractSpec = Tokens.DAY_OF_WEEK;
                    break;

                case Tokens.DAYOFYEAR :
                    function.extractSpec = Tokens.DAY_OF_YEAR;
                    break;

                case Tokens.WEEK :
                    function.extractSpec = Tokens.WEEK_OF_YEAR;
                    break;

                default :
                    function.extractSpec = tokenType;
            }
        }

        if (function.name == null) {
            function.name = token;
        }

        return function;
    }

    public static boolean isRegularFunction(int tokenType) {
        return customRegularFuncMap.get(tokenType, -1) != -1;
    }

    public static boolean isValueFunction(int tokenType) {
        return customValueFuncMap.get(tokenType, -1) != -1;
    }

    private FunctionCustom(int id) {

        super();

        this.funcType   = id;
        isDeterministic = !nonDeterministicFuncSet.contains(id);

        switch (id) {

            case FUNC_SQLCODE :
            case FUNC_SQLERRM :
                parseList = optionalNoParamList;
                break;

            case FUNC_DBTIMEZONE :
            case FUNC_SESSIONTIMEZONE :
            case FUNC_SYSDATE :
            case FUNC_SYSTIMESTAMP :
                parseList = optionalNoParamList;
                break;

            case FUNC_ACTION_ID :
            case FUNC_DATABASE :
            case FUNC_DATABASE_ISOLATION_LEVEL :
            case FUNC_DATABASE_NAME :
            case FUNC_DATABASE_TIMEZONE :
            case FUNC_DATABASE_VERSION :
            case FUNC_ISAUTOCOMMIT :
            case FUNC_ISOLATION_LEVEL :
            case FUNC_ISREADONLYDATABASE :
            case FUNC_ISREADONLYDATABASEFILES :
            case FUNC_ISREADONLYSESSION :
            case FUNC_PI :
            case FUNC_SESSION_ID :
            case FUNC_SESSION_ISOLATION_LEVEL :
            case FUNC_SESSION_TIMEZONE :
            case FUNC_TIMEZONE :
            case FUNC_TRANSACTION_CONTROL :
            case FUNC_TRANSACTION_ID :
            case FUNC_TRANSACTION_SIZE :
                parseList = emptyParamList;
                break;

            case FUNC_ACOS :
            case FUNC_ASCII :
            case FUNC_ASIN :
            case FUNC_ATAN :
            case FUNC_BITNOT :
            case FUNC_CHAR :
            case FUNC_COS :
            case FUNC_COSH :
            case FUNC_COT :
            case FUNC_DEGREES :
            case FUNC_DAYS :
            case FUNC_FROM_BASE64 :
            case FUNC_HEX :
            case FUNC_HEXTORAW :
            case FUNC_LAST_DAY :
            case FUNC_LOB_ID :
            case FUNC_LOG10 :
            case FUNC_RADIANS :
            case FUNC_RAWTOHEX :
            case FUNC_REVERSE :
            case FUNC_ROUNDMAGIC :
            case FUNC_SIGN :
            case FUNC_SIN :
            case FUNC_SINH :
            case FUNC_SOUNDEX :
            case FUNC_SPACE :
            case FUNC_SYS_EXTRACT_UTC :
            case FUNC_TAN :
            case FUNC_TANH :
            case FUNC_TIMESTAMP_WITH_ZONE :
            case FUNC_TO_BASE64 :
            case FUNC_TO_DSINTERVAL :
            case FUNC_TO_YMINTERVAL :
            case FUNC_TO_NUMBER :
            case FUNC_UNHEX :
                parseList = singleParamList;
                break;

            case FUNC_ADD_MONTHS :
            case FUNC_ATAN2 :
            case FUNC_CONCAT :
            case FUNC_CRYPT_KEY :
            case FUNC_BITAND :
            case FUNC_BITANDNOT :
            case FUNC_BITOR :
            case FUNC_BITXOR :
            case FUNC_DIFFERENCE :
            case FUNC_FROM_TZ :
            case FUNC_LEFT :
            case FUNC_MONTHS_BETWEEN :
            case FUNC_NANVL :
            case FUNC_NEXT_DAY :
            case FUNC_NUMTODSINTERVAL :
            case FUNC_NUMTOYMINTERVAL :
            case FUNC_REGEXP_MATCHES :
            case FUNC_REGEXP_SUBSTRING :
            case FUNC_REGEXP_SUBSTRING_ARRAY :
            case FUNC_REPEAT :
            case FUNC_RIGHT :
                parseList = doubleParamList;
                break;

            case FUNC_TO_CHAR :
            case FUNC_TO_DATE :
            case FUNC_TO_TIMESTAMP :
            case FUNC_TO_TIMESTAMP_TZ :
                parseList = optionalDoubleParamList;
                break;

            case FUNC_LOAD_FILE :
            case FUNC_ROUND :
            case FUNC_TIMESTAMP :
            case FUNC_TRUNC :
            case FUNC_TRUNCATE :
                parseList = optionalDoubleParamList;
                break;

            case FUNC_DATEDIFF :
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_TOKEN, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
                    Tokens.CLOSEBRACKET
                };
                parseListAlt = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.X_OPTION, 2, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                break;

            case FUNC_DATEADD :
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_TOKEN, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
                    Tokens.CLOSEBRACKET
                };
                parseListAlt = tripleParamList;
                break;

            case FUNC_DATENAME :
            case FUNC_DATEPART :
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_TOKEN, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                break;

            case FUNC_DATE_ADD :
            case FUNC_DATE_SUB :
                parseList = doubleParamList;
                break;

            case FUNC_NEW_TIME :
            case FUNC_SEQUENCE_ARRAY :
            case FUNC_TRANSLATE :
                parseList = tripleParamList;
                break;

            case FUNC_LPAD :
            case FUNC_RPAD :
            case FUNC_POSITION_CHAR :
            case FUNC_REPLACE :
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.X_OPTION, 2, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                break;

            case FUNC_REGEXP_REPLACE :
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.X_OPTION, 2, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.X_OPTION, 2, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.X_OPTION, 2, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.X_OPTION, 2, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                break;

            case FUNC_UNIX_MILLIS :
            case FUNC_UNIX_TIMESTAMP :
            case FUNC_UUID :
                parseList = optionalSingleParamList;
                break;

            case FUNC_EXTRACT :
                name      = Tokens.T_EXTRACT;
                parseList = singleParamList;
                break;

            case FUNC_TRIM_CHAR :
                name      = Tokens.T_TRIM;
                parseList = singleParamList;
                break;

            case FUNC_OVERLAY_CHAR :
                name      = Tokens.T_OVERLAY;
                parseList = quadParamList;
                break;

            case FUNC_IDENTITY :
                name      = Tokens.T_IDENTITY;
                parseList = emptyParamList;
                break;

            case FUNC_DIAGNOSTICS :
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.ROW_COUNT, Tokens.CLOSEBRACKET
                };
                break;

            case FUNC_POSITION_ARRAY :
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.IN,
                    Tokens.QUESTION, Tokens.X_OPTION, 2, Tokens.FROM,
                    Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                break;

            case FUNC_SORT_ARRAY :
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.X_OPTION, 4,
                    Tokens.X_KEYSET, 2, Tokens.ASC, Tokens.DESC,
                    Tokens.X_OPTION, 5, Tokens.NULLS, Tokens.X_KEYSET, 2,
                    Tokens.FIRST, Tokens.LAST, Tokens.CLOSEBRACKET
                };
                break;

            case FUNC_TIMESTAMPADD :
                name      = Tokens.T_TIMESTAMPADD;
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_KEYSET, 10,
                    Tokens.SQL_TSI_FRAC_SECOND, Tokens.SQL_TSI_MILLI_SECOND,
                    Tokens.SQL_TSI_SECOND, Tokens.SQL_TSI_MINUTE,
                    Tokens.SQL_TSI_HOUR, Tokens.SQL_TSI_DAY,
                    Tokens.SQL_TSI_WEEK, Tokens.SQL_TSI_MONTH,
                    Tokens.SQL_TSI_QUARTER, Tokens.SQL_TSI_YEAR, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
                    Tokens.CLOSEBRACKET
                };
                parseListAlt = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_KEYSET, 10,
                    Tokens.MICROSECOND, Tokens.MILLISECOND, Tokens.SECOND,
                    Tokens.MINUTE, Tokens.HOUR, Tokens.DAY, Tokens.WEEK,
                    Tokens.MONTH, Tokens.QUARTER, Tokens.YEAR, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
                    Tokens.CLOSEBRACKET
                };
                break;

            case FUNC_TIMESTAMPDIFF :
                name      = Tokens.T_TIMESTAMPDIFF;
                parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_KEYSET, 10,
                    Tokens.SQL_TSI_FRAC_SECOND, Tokens.SQL_TSI_MILLI_SECOND,
                    Tokens.SQL_TSI_SECOND, Tokens.SQL_TSI_MINUTE,
                    Tokens.SQL_TSI_HOUR, Tokens.SQL_TSI_DAY,
                    Tokens.SQL_TSI_WEEK, Tokens.SQL_TSI_MONTH,
                    Tokens.SQL_TSI_QUARTER, Tokens.SQL_TSI_YEAR, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
                    Tokens.CLOSEBRACKET
                };
                parseListAlt = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_KEYSET, 10,
                    Tokens.MICROSECOND, Tokens.MILLISECOND, Tokens.SECOND,
                    Tokens.MINUTE, Tokens.HOUR, Tokens.DAY, Tokens.WEEK,
                    Tokens.MONTH, Tokens.QUARTER, Tokens.YEAR, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
                    Tokens.CLOSEBRACKET
                };
                break;

            case FUNC_RAND :
                parseList = optionalSingleParamList;
                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "FunctionCustom");
        }
    }

    public void setArguments(Expression[] nodes) {

        switch (funcType) {

            case FUNC_POSITION_CHAR : {
                Expression[] newNodes = new Expression[4];

                if (Tokens.T_LOCATE.equals(name)) {

                    // LOCATE
                    newNodes[0] = nodes[0];
                    newNodes[1] = nodes[1];
                    newNodes[3] = nodes[2];
                    nodes       = newNodes;
                } else if (Tokens.T_INSTR.equals(name)) {
                    newNodes[0] = nodes[1];
                    newNodes[1] = nodes[0];
                    newNodes[3] = nodes[2];
                    nodes       = newNodes;
                }

                break;
            }
            case FUNC_OVERLAY_CHAR : {
                Expression start  = nodes[1];
                Expression length = nodes[2];

                nodes[1] = nodes[3];
                nodes[2] = start;
                nodes[3] = length;

                break;
            }
            case FUNC_EXTRACT : {
                Expression[] newNodes = new Expression[2];

                newNodes[0] =
                    new ExpressionValue(ValuePool.getInt(extractSpec),
                                        Type.SQL_INTEGER);
                newNodes[1] = nodes[0];
                nodes       = newNodes;

                break;
            }
            case FUNC_TRIM_CHAR : {
                Expression[] newNodes = new Expression[3];

                newNodes[0] =
                    new ExpressionValue(ValuePool.getInt(extractSpec),
                                        Type.SQL_INTEGER);
                newNodes[1] = new ExpressionValue(" ", Type.SQL_CHAR);
                newNodes[2] = nodes[0];
                nodes       = newNodes;
            }
        }

        super.setArguments(nodes);
    }

    public Expression getFunctionExpression() {

        switch (funcType) {

            case FUNC_CONCAT :
                return new ExpressionArithmetic(OpTypes.CONCAT,
                                                nodes[Expression.LEFT],
                                                nodes[Expression.RIGHT]);
        }

        return super.getFunctionExpression();
    }

    Object getValue(Session session, Object[] data) {

        switch (funcType) {

            case FUNC_SQLCODE :
                return 0;

            case FUNC_SQLERRM :
                return "Error";

            case FUNC_POSITION_CHAR :
            case FUNC_EXTRACT :
            case FUNC_TRIM_CHAR :
            case FUNC_OVERLAY_CHAR :
                return super.getValue(session, data);

            case FUNC_DATABASE :
                return session.getDatabase().getPath();

            case FUNC_DATABASE_NAME :
                return session.getDatabase().getNameString();

            case FUNC_ISAUTOCOMMIT :
                return session.isAutoCommit() ? Boolean.TRUE
                                              : Boolean.FALSE;

            case FUNC_ISREADONLYSESSION :
                return session.isReadOnlyDefault() ? Boolean.TRUE
                                                   : Boolean.FALSE;

            case FUNC_ISREADONLYDATABASE :
                return session.getDatabase().databaseReadOnly ? Boolean.TRUE
                                                              : Boolean.FALSE;

            case FUNC_ISREADONLYDATABASEFILES :
                return session.getDatabase().isFilesReadOnly() ? Boolean.TRUE
                                                               : Boolean.FALSE;

            case FUNC_ISOLATION_LEVEL : {
                return Session.getIsolationString(session.isolationLevel);
            }
            case FUNC_SESSION_ISOLATION_LEVEL :
                return Session.getIsolationString(
                    session.isolationLevelDefault);

            case FUNC_DATABASE_ISOLATION_LEVEL :
                return Session.getIsolationString(
                    session.database.defaultIsolationLevel);

            case FUNC_TRANSACTION_CONTROL :
                switch (session.database.txManager.getTransactionControl()) {

                    case TransactionManager.MVCC :
                        return Tokens.T_MVCC;

                    case TransactionManager.MVLOCKS :
                        return Tokens.T_MVLOCKS;

                    case TransactionManager.LOCKS :
                    default :
                        return Tokens.T_LOCKS;
                }
            case FUNC_TIMEZONE :
                return new IntervalSecondData(session.getZoneSeconds(), 0);

            case FUNC_SESSION_TIMEZONE :
                return new IntervalSecondData(session.sessionTimeZoneSeconds,
                                              0);

            case FUNC_DBTIMEZONE : {
                TimestampData timestamp = session.getSystemTimestamp(true);
                IntervalSecondData zone =
                    new IntervalSecondData(timestamp.getZone(), 0);

                return Type.SQL_INTERVAL_HOUR_TO_MINUTE.convertToString(zone);
            }
            case FUNC_DATABASE_TIMEZONE :
                int sec =
                    HsqlDateTime.getZoneSeconds(HsqlDateTime.tempCalDefault);

                return new IntervalSecondData(sec, 0);

            case FUNC_DATABASE_VERSION :
                return HsqlDatabaseProperties.THIS_FULL_VERSION;

            case FUNC_SESSION_ID : {
                return Long.valueOf(session.getId());
            }
            case FUNC_ACTION_ID : {
                return Long.valueOf(session.actionTimestamp);
            }
            case FUNC_TRANSACTION_ID : {
                return Long.valueOf(session.transactionTimestamp);
            }
            case FUNC_TRANSACTION_SIZE : {
                return Long.valueOf(session.actionIndex);
            }
            case FUNC_LOB_ID : {
                LobData lob = (LobData) data[0];

                if (lob == null) {
                    return null;
                }

                return Long.valueOf(lob.getId());
            }
            case FUNC_IDENTITY : {
                Number id = session.getLastIdentity();

                if (id instanceof Long) {
                    return id;
                } else {
                    return ValuePool.getLong(id.longValue());
                }
            }
            case FUNC_DIAGNOSTICS : {
                return session.sessionContext
                    .diagnosticsVariables[exprSubType];
            }
            case FUNC_SEQUENCE_ARRAY : {
                for (int i = 0; i < data.length; i++) {
                    if (data[i] == null) {
                        return null;
                    }
                }

                HsqlArrayList list    = new HsqlArrayList();
                Object        current = data[0];
                Type          type    = nodes[0].getDataType();
                boolean ascending = type.compare(session, data[1], data[0])
                                    >= 0;

                while (true) {
                    int compare = type.compare(session, current, data[1]);

                    if (ascending) {
                        if (compare > 0) {
                            break;
                        }
                    } else if (compare < 0) {
                        break;
                    }

                    list.add(current);

                    Object newValue = type.add(session, current, data[2],
                                               nodes[2].getDataType());

                    compare = type.compare(session, current, newValue);

                    if (ascending) {
                        if (compare >= 0) {
                            break;
                        }
                    } else if (compare <= 0) {
                        break;
                    }

                    current = newValue;
                }

                Object[] array = list.toArray();

                return array;
            }
            case FUNC_TIMESTAMPADD : {
                if (data[1] == null || data[2] == null) {
                    return null;
                }

                data[1] = Type.SQL_BIGINT.convertToType(session, data[1],
                        nodes[1].getDataType());

                int           part = ((Number) nodes[0].valueData).intValue();
                long          units  = ((Number) data[1]).longValue();
                TimestampData source = (TimestampData) data[2];
                IntervalType  t;
                Object        o;

                switch (part) {

                    case Tokens.NANOSECOND :
                    case Tokens.SQL_TSI_FRAC_SECOND : {
                        long seconds = units / DTIType.limitNanoseconds;
                        int  nanos = (int) (units % DTIType.limitNanoseconds);

                        t = Type
                            .SQL_INTERVAL_SECOND_MAX_FRACTION_MAX_PRECISION;
                        o = new IntervalSecondData(seconds, nanos, t);

                        return dataType.add(session, source, o, t);
                    }
                    case Tokens.MICROSECOND :
                    case Tokens.SQL_TSI_MICRO_SECOND : {
                        long seconds = units / 1000000;
                        int  nanos   = (int) (units % 1000000) * 1000;

                        t = Type
                            .SQL_INTERVAL_SECOND_MAX_FRACTION_MAX_PRECISION;
                        o = new IntervalSecondData(seconds, nanos, t);

                        return dataType.add(session, source, o, t);
                    }
                    case Tokens.MILLISECOND :
                    case Tokens.SQL_TSI_MILLI_SECOND : {
                        long seconds = units / 1000;
                        int  nanos   = (int) (units % 1000) * 1000000;

                        t = Type
                            .SQL_INTERVAL_SECOND_MAX_FRACTION_MAX_PRECISION;
                        o = new IntervalSecondData(seconds, nanos, t);

                        return dataType.add(session, source, o, t);
                    }
                    case Tokens.SECOND :
                    case Tokens.SQL_TSI_SECOND :
                        t = Type
                            .SQL_INTERVAL_SECOND_MAX_FRACTION_MAX_PRECISION;
                        o = IntervalSecondData.newIntervalSeconds(units, t);

                        return dataType.add(session, source, o, t);

                    case Tokens.MINUTE :
                    case Tokens.SQL_TSI_MINUTE :
                        t = Type.SQL_INTERVAL_MINUTE_MAX_PRECISION;
                        o = IntervalSecondData.newIntervalMinute(units, t);

                        return dataType.add(session, source, o, t);

                    case Tokens.HOUR :
                    case Tokens.SQL_TSI_HOUR :
                        t = Type.SQL_INTERVAL_HOUR_MAX_PRECISION;
                        o = IntervalSecondData.newIntervalHour(units, t);

                        return dataType.add(session, source, o, t);

                    case Tokens.DAY :
                    case Tokens.DAY_OF_YEAR :
                    case Tokens.DAY_OF_WEEK :
                    case Tokens.SQL_TSI_DAY :
                        t = Type.SQL_INTERVAL_DAY_MAX_PRECISION;
                        o = IntervalSecondData.newIntervalDay(units, t);

                        return dataType.add(session, source, o, t);

                    case Tokens.WEEK :
                    case Tokens.SQL_TSI_WEEK :
                        t = Type.SQL_INTERVAL_DAY_MAX_PRECISION;
                        o = IntervalSecondData.newIntervalDay(units * 7, t);

                        return dataType.add(session, source, o, t);

                    case Tokens.MONTH :
                    case Tokens.SQL_TSI_MONTH :
                        t = Type.SQL_INTERVAL_MONTH_MAX_PRECISION;
                        o = IntervalMonthData.newIntervalMonth(units, t);

                        return dataType.add(session, source, o, t);

                    case Tokens.QUARTER :
                    case Tokens.SQL_TSI_QUARTER :
                        t = Type.SQL_INTERVAL_MONTH_MAX_PRECISION;
                        o = IntervalMonthData.newIntervalMonth(units * 3, t);

                        return dataType.add(session, source, o, t);

                    case Tokens.YEAR :
                    case Tokens.SQL_TSI_YEAR :
                        t = Type.SQL_INTERVAL_YEAR_MAX_PRECISION;
                        o = IntervalMonthData.newIntervalMonth(units * 12, t);

                        return dataType.add(session, source, o, t);

                    default :
                        throw Error.runtimeError(ErrorCode.U_S0500,
                                                 "FunctionCustom");
                }
            }
            case FUNC_TIMESTAMPDIFF : {
                if (data[1] == null || data[2] == null) {
                    return null;
                }

                int           part = ((Number) nodes[0].valueData).intValue();
                TimestampData a    = (TimestampData) data[2];
                TimestampData b    = (TimestampData) data[1];

                if (nodes[2].dataType.isDateTimeTypeWithZone()) {
                    a = (TimestampData) Type.SQL_TIMESTAMP.convertToType(
                        session, a, Type.SQL_TIMESTAMP_WITH_TIME_ZONE);
                }

                if (nodes[1].dataType.isDateTimeTypeWithZone()) {
                    b = (TimestampData) Type.SQL_TIMESTAMP.convertToType(
                        session, b, Type.SQL_TIMESTAMP_WITH_TIME_ZONE);
                }

                IntervalType t;
                long         ret;

                switch (part) {

                    case Tokens.NANOSECOND :
                    case Tokens.SQL_TSI_FRAC_SECOND : {
                        t = Type.SQL_INTERVAL_SECOND_MAX_PRECISION;

                        IntervalSecondData interval =
                            (IntervalSecondData) t.subtract(session, a, b,
                                                            null);

                        return new Long(
                            DTIType.limitNanoseconds * interval.getSeconds()
                            + interval.getNanos());
                    }
                    case Tokens.MICROSECOND : {
                        t = Type.SQL_INTERVAL_SECOND_MAX_PRECISION;

                        IntervalSecondData interval =
                            (IntervalSecondData) t.subtract(session, a, b,
                                                            null);

                        return new Long(1000000 * interval.getSeconds()
                                        + interval.getNanos() / 1000);
                    }
                    case Tokens.MILLISECOND :
                    case Tokens.SQL_TSI_MILLI_SECOND : {
                        t = Type.SQL_INTERVAL_SECOND_MAX_PRECISION;

                        IntervalSecondData interval =
                            (IntervalSecondData) t.subtract(session, a, b,
                                                            null);

                        return new Long(1000 * interval.getSeconds()
                                        + interval.getNanos() / 1000000);
                    }
                    case Tokens.SECOND :
                    case Tokens.SQL_TSI_SECOND :
                        t = Type.SQL_INTERVAL_SECOND_MAX_PRECISION;

                        return new Long(
                            t.convertToLongEndUnits(
                                t.subtract(session, a, b, null)));

                    case Tokens.MINUTE :
                    case Tokens.SQL_TSI_MINUTE :
                        t = Type.SQL_INTERVAL_MINUTE_MAX_PRECISION;

                        return new Long(
                            t.convertToLongEndUnits(
                                t.subtract(session, a, b, null)));

                    case Tokens.HOUR :
                    case Tokens.SQL_TSI_HOUR :
                        t = Type.SQL_INTERVAL_HOUR_MAX_PRECISION;

                        return new Long(
                            t.convertToLongEndUnits(
                                t.subtract(session, a, b, null)));

                    case Tokens.DAY :
                    case Tokens.SQL_TSI_DAY :
                        t = Type.SQL_INTERVAL_DAY_MAX_PRECISION;

                        return new Long(
                            t.convertToLongEndUnits(
                                t.subtract(session, a, b, null)));

                    case Tokens.WEEK :
                    case Tokens.SQL_TSI_WEEK :
                        t = Type.SQL_INTERVAL_DAY_MAX_PRECISION;
                        ret = t.convertToLongEndUnits(t.subtract(session, a,
                                b, null));
                        ret = ret < 0 ? (ret - 6) / 7
                                      : (ret + 6) / 7;

                        return new Long(ret);

                    case Tokens.MONTH :
                    case Tokens.SQL_TSI_MONTH :
                        t = Type.SQL_INTERVAL_MONTH_MAX_PRECISION;

                        return new Long(
                            t.convertToLongEndUnits(
                                t.subtract(session, a, b, null)));

                    case Tokens.QUARTER :
                    case Tokens.SQL_TSI_QUARTER :
                        t = Type.SQL_INTERVAL_MONTH_MAX_PRECISION;
                        ret = t.convertToLongEndUnits(t.subtract(session, a,
                                b, null));
                        ret = ret < 0 ? (ret - 2) / 3
                                      : (ret + 2) / 3;

                        return new Long(ret);

                    case Tokens.YEAR :
                    case Tokens.SQL_TSI_YEAR :
                        t = Type.SQL_INTERVAL_YEAR_MAX_PRECISION;

                        return new Long(
                            t.convertToLongEndUnits(
                                t.subtract(session, a, b, null)));

                    default :
                        throw Error.runtimeError(ErrorCode.U_S0500,
                                                 "FunctionCustom");
                }
            }
            case FUNC_DATENAME : {
                Object value = getExtractValue(session, data);

                // todo - convert WEEKDAY to Monday, .., MONTH to October
                return dataType.convertToDefaultType(session, value);
            }
            case FUNC_DATE_ADD : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }

                return dataType.add(session, data[0], data[1],
                                    nodes[RIGHT].dataType);
            }
            case FUNC_DATE_SUB : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }

                return dataType.subtract(session, data[0], data[1],
                                         nodes[RIGHT].dataType);
            }
            case FUNC_DAYS : {
                if (data[0] == null) {
                    return null;
                }

                IntervalSecondData diff =
                    (IntervalSecondData) Type.SQL_INTERVAL_DAY_MAX_PRECISION
                        .subtract(session, data[0],
                                  DateTimeType.epochTimestamp, Type.SQL_DATE);

                return ValuePool.getInt((int) (diff.getSeconds()
                                               / (24 * 60 * 60) + 1));
            }
            case FUNC_PI :
                return new Double(Math.PI);

            case FUNC_RAND : {
                if (nodes[0] == null) {
                    return new Double(session.random());
                } else {
                    data[0] = Type.SQL_BIGINT.convertToType(session, data[0],
                            nodes[0].getDataType());

                    long seed = ((Number) data[0]).longValue();

                    return new Double(session.random(seed));
                }
            }
            case FUNC_ROUND :
            case FUNC_TRUNC : {
                int interval = Types.SQL_INTERVAL_DAY;

                if (data[0] == null) {
                    return null;
                }

                if (dataType.isDateTimeType()) {
                    DateTimeType type = (DateTimeType) dataType;

                    if (nodes.length > 1 && nodes[1] != null) {
                        if (data[1] == null) {
                            return null;
                        }

                        interval = HsqlDateTime.toStandardIntervalPart(
                            (String) data[1]);
                    }

                    if (interval < 0) {
                        throw Error.error(ErrorCode.X_42566, (String) data[1]);
                    }

                    return funcType == FUNC_ROUND
                           ? type.round(data[0], interval)
                           : type.truncate(data[0], interval);
                }
            }

            // fall through
            case FUNC_TRUNCATE : {
                int offset = 0;

                if (data[0] == null) {
                    return null;
                }

                if (nodes.length > 1) {
                    if (data[1] == null) {
                        return null;
                    }

                    data[1] = Type.SQL_INTEGER.convertToType(session, data[1],
                            nodes[1].getDataType());
                    offset = ((Number) data[1]).intValue();
                }

                return funcType == FUNC_ROUND
                       ? ((NumberType) dataType).round(data[0], offset)
                       : ((NumberType) dataType).truncate(data[0], offset);
            }
            case FUNC_FROM_BASE64 : {

//#ifdef JAVA8
/*
                String val = (String) data[0];

                if (val == null) {
                    return null;
                }

                val = val.trim();

                byte[] bytes = java.util.Base64.getDecoder().decode(val);

                return new BinaryData(bytes, false);
*/

//#else
                throw Error.error(ErrorCode.X_0A501);

//#endif JAVA8
            }
            case FUNC_TO_BASE64 : {

//#ifdef JAVA8
/*
                BinaryData val = (BinaryData) data[0];

                if (val == null) {
                    return null;
                }

                return java.util.Base64.getEncoder().encodeToString(val.getBytes());
*/

//#else
                throw Error.error(ErrorCode.X_0A501);

//#endif JAVA8
            }
            case FUNC_TO_CHAR : {
                if (nodes[1] == null) {
                    return dataType.convertToType(session, data[0],
                                                  nodes[0].dataType);
                } else {
                    if (data[0] == null || data[1] == null) {
                        return null;
                    }

                    SimpleDateFormat format = session.getSimpleDateFormatGMT();
                    Date date =
                        (Date) ((DateTimeType) nodes[0].dataType)
                            .convertSQLToJavaGMT(session, data[0]);

                    return HsqlDateTime.toFormattedDate(date,
                                                        (String) data[1],
                                                        format);
                }
            }
            case FUNC_TO_NUMBER : {
                if (data[0] == null) {
                    return null;
                }

                return dataType.convertToType(session, data[0],
                                              nodes[0].dataType);
            }
            case FUNC_TO_DATE :
            case FUNC_TO_TIMESTAMP : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }

                if (nodes[0].dataType.isDateOrTimestampType()) {
                    return dataType.convertToType(session, data[0],
                                                  nodes[0].dataType);
                }

                boolean          fraction = funcType == FUNC_TO_TIMESTAMP;
                SimpleDateFormat format   = session.getSimpleDateFormatGMT();
                TimestampData value = HsqlDateTime.toDate((String) data[0],
                    (String) data[1], format, fraction);

                return value;
            }
            case FUNC_TIMESTAMP : {
                boolean unary = nodes[1] == null;

                if (data[0] == null) {
                    return null;
                }

                if (unary) {
                    if (nodes[0].dataType.isNumberType()) {
                        return new TimestampData(
                            ((Number) data[0]).longValue());
                    }

                    try {
                        return Type.SQL_TIMESTAMP.convertToType(session,
                                data[0], nodes[0].dataType);
                    } catch (HsqlException e) {
                        return Type.SQL_DATE.convertToType(session, data[0],
                                                           nodes[0].dataType);
                    }
                }

                if (data[1] == null) {
                    return null;
                }

                TimestampData date =
                    (TimestampData) Type.SQL_DATE.convertToType(session,
                        data[0], nodes[0].dataType);
                TimeData time = (TimeData) Type.SQL_TIME.convertToType(session,
                    data[1], nodes[1].dataType);

                return new TimestampData(date.getSeconds()
                                         + time.getSeconds(), time.getNanos());
            }
            case FUNC_TIMESTAMP_WITH_ZONE : {
                Calendar calendar = session.getCalendar();
                long     seconds;
                int      nanos = 0;
                int      zone;

                if (data[0] == null) {
                    return null;
                }

                if (nodes[0].dataType.isNumberType()) {
                    seconds = ((Number) data[0]).longValue();
                } else if (nodes[0].dataType.typeCode == Types.SQL_TIMESTAMP) {
                    seconds = ((TimestampData) data[0]).getSeconds();
                    seconds =
                        HsqlDateTime.convertMillisToCalendar(
                            calendar, seconds * 1000) / 1000;
                } else if (nodes[0].dataType.typeCode
                           == Types.SQL_TIMESTAMP_WITH_TIME_ZONE) {
                    seconds = ((TimestampData) data[0]).getSeconds();
                } else {
                    throw Error.error(ErrorCode.X_42566, (String) data[1]);
                }

                synchronized (calendar) {
                    calendar.setTimeInMillis(seconds * 1000);

                    zone = HsqlDateTime.getZoneSeconds(calendar);
                }

                return new TimestampData(seconds, nanos, zone);
            }
            case FUNC_UUID : {
                if (nodes[0] == null) {
                    UUID uuid = java.util.UUID.randomUUID();
                    long hi   = uuid.getMostSignificantBits();
                    long lo   = uuid.getLeastSignificantBits();

                    return new BinaryData(ArrayUtil.toByteArray(hi, lo),
                                          false);
                } else {
                    if (data[0] == null) {
                        return null;
                    }

                    try {
                        if (dataType.isBinaryType()) {
                            byte[] bytes =
                                StringConverter.toBinaryUUID((String) data[0]);

                            return new BinaryData(bytes, false);
                        } else {
                            return StringConverter.toStringUUID(
                                ((BinaryData) data[0]).getBytes());
                        }
                    } catch (NumberFormatException e) {
                        throw Error.error(ErrorCode.X_22026);
                    }
                }
            }
            case FUNC_UNIX_MILLIS : {
                TimestampData ts;

                if (nodes[0] == null) {
                    ts = session.getCurrentTimestamp(true);
                } else {
                    if (data[0] == null) {
                        return null;
                    }

                    ts = (TimestampData) data[0];
                }

                long millis = ts.getSeconds() * 1000 + ts.getNanos() / 1000000;

                return Long.valueOf(millis);
            }
            case FUNC_UNIX_TIMESTAMP : {
                TimestampData ts;

                if (nodes[0] == null) {
                    ts = session.getCurrentTimestamp(true);
                } else {
                    if (data[0] == null) {
                        return null;
                    }

                    ts = (TimestampData) data[0];
                }

                return Long.valueOf(ts.getSeconds());
            }
            case FUNC_ACOS : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.acos(d));
            }
            case FUNC_ASIN : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.asin(d));
            }
            case FUNC_ATAN : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.atan(d));
            }
            case FUNC_COS : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.cos(d));
            }
            case FUNC_COSH : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.cosh(d));
            }
            case FUNC_COT : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);
                double c = 1.0 / java.lang.Math.tan(d);

                return new Double(c);
            }
            case FUNC_DEGREES : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.toDegrees(d));
            }
            case FUNC_SIN : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.sin(d));
            }
            case FUNC_SINH : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.sinh(d));
            }
            case FUNC_TAN : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.tan(d));
            }
            case FUNC_TANH : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.tanh(d));
            }
            case FUNC_LOG10 : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                if (d < 0) {
                    if (session.database.sqlDoubleNaN) {
                        throw Error.error(ErrorCode.X_2201E);
                    }
                }

                return new Double(java.lang.Math.log10(d));
            }
            case FUNC_RADIANS : {
                if (data[0] == null) {
                    return null;
                }

                double d = NumberType.toDouble(data[0]);

                return new Double(java.lang.Math.toRadians(d));
            }

            //
            case FUNC_SIGN : {
                if (data[0] == null) {
                    return null;
                }

                if (session.database.sqlSyntaxOra) {
                    if (data[0] instanceof Double) {
                        if (((Double) data[0]).doubleValue() == 0d) {
                            return Integer.valueOf(1);
                        }
                    }
                }

                int val =
                    ((NumberType) nodes[0].dataType).compareToZero(data[0]);

                return Integer.valueOf(val);
            }
            case FUNC_ATAN2 : {
                if (data[0] == null) {
                    return null;
                }

                double a = NumberType.toDouble(data[0]);
                double b = NumberType.toDouble(data[1]);

                return new Double(java.lang.Math.atan2(a, b));
            }
            case FUNC_ASCII : {
                String arg;

                if (data[0] == null) {
                    return null;
                }

                if (nodes[0].dataType.isLobType()) {
                    arg = ((ClobData) data[0]).getSubString(session, 0, 1);
                } else {
                    arg = (String) data[0];
                }

                if (arg.length() == 0) {
                    return null;
                }

                return ValuePool.getInt(arg.charAt(0));
            }
            case FUNC_CHAR :
                if (data[0] == null) {
                    return null;
                }

                data[0] = Type.SQL_INTEGER.convertToType(session, data[0],
                        nodes[0].getDataType());

                int arg = ((Number) data[0]).intValue();

                if (Character.isValidCodePoint(arg)
                        && Character.isValidCodePoint((char) arg)) {
                    return String.valueOf((char) arg);
                }

                throw Error.error(ErrorCode.X_22511);
            case FUNC_ROUNDMAGIC : {
                int offset = 0;

                if (data[0] == null) {
                    return null;
                }

                if (nodes.length > 1) {
                    if (data[1] == null) {
                        return null;
                    }

                    offset = ((Number) data[1]).intValue();
                }

                return ((NumberType) dataType).round(data[0], offset);
            }
            case FUNC_SOUNDEX : {
                if (data[0] == null) {
                    return null;
                }

                String s = (String) data[0];

                return new String(soundex(s), 0, 4);
            }
            case FUNC_BITAND :
            case FUNC_BITANDNOT :
            case FUNC_BITNOT :
            case FUNC_BITOR :
            case FUNC_BITXOR : {
                for (int i = 0; i < data.length; i++) {
                    if (data[i] == null) {
                        return null;
                    }
                }

                if (dataType.isNumberType()) {
                    long v = 0;
                    long a;
                    long b = 0;

                    data[0] = Type.SQL_BIGINT.convertToType(session, data[0],
                            nodes[0].getDataType());
                    a = ((Number) data[0]).longValue();

                    if (funcType != FUNC_BITNOT) {
                        data[1] = Type.SQL_BIGINT.convertToType(session,
                                data[1], nodes[1].getDataType());
                        b = ((Number) data[1]).longValue();
                    }

                    switch (funcType) {

                        case FUNC_BITAND :
                            v = a & b;
                            break;

                        case FUNC_BITANDNOT :
                            v = a & ~b;
                            break;

                        case FUNC_BITNOT :
                            v = ~a;
                            break;

                        case FUNC_BITOR :
                            v = a | b;
                            break;

                        case FUNC_BITXOR :
                            v = a ^ b;
                            break;

                        default :
                    }

                    switch (dataType.typeCode) {

                        case Types.SQL_NUMERIC :
                        case Types.SQL_DECIMAL :
                            return BigDecimal.valueOf(v);

                        case Types.SQL_BIGINT :
                            return ValuePool.getLong(v);

                        case Types.SQL_INTEGER :
                        case Types.SQL_SMALLINT :
                        case Types.TINYINT :
                            return ValuePool.getInt((int) v);

                        default :
                            throw Error.error(ErrorCode.X_42561);
                    }
                } else {
                    byte[] a = ((BinaryData) data[0]).getBytes();
                    byte[] b = null;
                    byte[] v;

                    if (funcType != FUNC_BITNOT) {
                        b = ((BinaryData) data[1]).getBytes();
                    }

                    switch (funcType) {

                        case FUNC_BITAND :
                            v = BitMap.and(a, b);
                            break;

                        case FUNC_BITANDNOT :
                            b = BitMap.not(b);
                            v = BitMap.and(a, b);
                            break;

                        case FUNC_BITNOT :
                            v = BitMap.not(a);
                            break;

                        case FUNC_BITOR :
                            v = BitMap.or(a, b);
                            break;

                        case FUNC_BITXOR :
                            v = BitMap.xor(a, b);
                            break;

                        default :
                            throw Error.error(ErrorCode.X_42561);
                    }

                    return new BinaryData(v, dataType.precision);
                }
            }
            case FUNC_DIFFERENCE : {
                for (int i = 0; i < data.length; i++) {
                    if (data[i] == null) {
                        return null;
                    }
                }

                char[] s1 = soundex((String) data[0]);
                char[] s2 = soundex((String) data[1]);
                int    e  = 0;

                if (s1[0] == s2[0]) {
                    e++;
                }

                int js = 1;

                for (int i = 1; i < 4; i++) {
                    for (int j = js; j < 4; j++) {
                        if (s1[j] == s2[i]) {
                            e++;

                            js = j + 1;

                            break;
                        }
                    }
                }

                return ValuePool.getInt(e);
            }
            case FUNC_HEX : {
                if (data[0] == null) {
                    return null;
                }

                if (nodes[0].dataType.isBinaryType()) {
                    return dataType.convertToType(session, data[0],
                                                  nodes[0].dataType);
                } else if (nodes[0].dataType.isNumberType()) {
                    long val = ((Number) data[0]).longValue();

                    return Long.toHexString(val);
                } else {
                    String val = ((String) data[0]);

                    try {
                        byte[] bytes = val.getBytes("ISO-8859-1");

                        return StringConverter.byteArrayToHexString(bytes);
                    } catch (UnsupportedEncodingException e) {
                        throw Error.error(ErrorCode.X_42561);
                    }
                }
            }
            case FUNC_UNHEX :
            case FUNC_HEXTORAW : {
                if (data[0] == null) {
                    return null;
                }

                return dataType.convertToType(session, data[0],
                                              nodes[0].dataType);
            }
            case FUNC_RAWTOHEX : {
                if (data[0] == null) {
                    return null;
                }

                BlobData binary = (BlobData) data[0];
                byte[] bytes = binary.getBytes(session, 0,
                                               (int) binary.length(session));

                return StringConverter.byteArrayToHexString(bytes);
            }
            case FUNC_REPEAT : {
                for (int i = 0; i < data.length; i++) {
                    if (data[i] == null) {
                        return null;
                    }
                }

                data[1] = Type.SQL_INTEGER.convertToType(session, data[1],
                        nodes[1].getDataType());

                String       string = (String) data[0];
                int          i      = ((Number) data[1]).intValue();
                StringBuffer sb     = new StringBuffer(string.length() * i);

                while (i-- > 0) {
                    sb.append(string);
                }

                return sb.toString();
            }
            case FUNC_REPLACE : {
                for (int i = 0; i < data.length; i++) {
                    if (data[i] == null) {
                        return null;
                    }
                }

                String       string  = (String) data[0];
                String       find    = (String) data[1];
                String       replace = (String) data[2];
                StringBuffer sb      = new StringBuffer();
                int          start   = 0;

                if (find.length() == 0) {
                    return string;
                }

                while (true) {
                    int i = string.indexOf(find, start);

                    if (i == -1) {
                        sb.append(string.substring(start));

                        break;
                    }

                    sb.append(string.substring(start, i));
                    sb.append(replace);

                    start = i + find.length();
                }

                return sb.toString();
            }
            case FUNC_LEFT :
            case FUNC_RIGHT : {
                for (int i = 0; i < data.length; i++) {
                    if (data[i] == null) {
                        return null;
                    }
                }

                int count = ((Number) data[1]).intValue();

                return ((CharacterType) dataType).substring(session, data[0],
                        0, count, true, funcType == FUNC_RIGHT);
            }
            case FUNC_SPACE : {
                if (data[0] == null) {
                    return null;
                }

                data[0] = Type.SQL_INTEGER.convertToType(session, data[0],
                        nodes[0].getDataType());

                int    count = ((Number) data[0]).intValue();
                char[] array = new char[count];

                ArrayUtil.fillArray(array, 0, ' ');

                return String.valueOf(array);
            }
            case FUNC_REVERSE : {
                if (data[0] == null) {
                    return null;
                }

                StringBuffer sb = new StringBuffer((String) data[0]);

                sb = sb.reverse();

                return sb.toString();
            }
            case FUNC_REGEXP_REPLACE : {
                for (int i = 0; i < nodes.length; i++) {
                    if (nodes[i] == null) {
                        break;
                    }

                    if (data[i] == null) {
                        return null;
                    }
                }

                int     flags          = this.regexpParams((String) data[5]);
                Pattern currentPattern = null;
                String  source         = (String) data[0];
                String  matchPattern   = (String) data[1];
                String  replace        = (String) data[2];
                boolean isFixed        = nodes[1].getType() == OpTypes.VALUE;
                int     start          = 1;
                int     count          = 0;

                if (nodes[4] != null) {
                    count = ((Number) data[4]).intValue();
                }

                if (isFixed) {
                    currentPattern = pattern;
                }

                if (currentPattern == null) {
                    currentPattern = Pattern.compile(matchPattern, flags);
                }

                if (isFixed) {
                    pattern = currentPattern;
                }

                Matcher matcher = currentPattern.matcher(source);
                String  result;

                if (start > 1) {
                    matcher.region(start - 1, source.length());
                }

                if (count == 0) {
                    result = matcher.replaceAll(replace);
                } else if (count == 1) {
                    result = matcher.replaceFirst(replace);
                } else {
                    throw Error.error(ErrorCode.X_22511, "count");
                }

                return result;
            }
            case FUNC_REGEXP_MATCHES :
            case FUNC_REGEXP_SUBSTRING :
            case FUNC_REGEXP_SUBSTRING_ARRAY : {
                for (int i = 0; i < data.length; i++) {
                    if (data[i] == null) {
                        return null;
                    }
                }

                Pattern currentPattern = pattern;

                if (currentPattern == null) {
                    String matchPattern = (String) data[1];

                    currentPattern = Pattern.compile(matchPattern);
                }

                Matcher matcher = currentPattern.matcher((String) data[0]);

                switch (funcType) {

                    case FUNC_REGEXP_MATCHES : {
                        boolean match = matcher.matches();

                        return Boolean.valueOf(match);
                    }
                    case FUNC_REGEXP_SUBSTRING : {
                        boolean match = matcher.find();

                        if (match) {
                            return matcher.group();
                        } else {
                            return null;
                        }
                    }
                    case FUNC_REGEXP_SUBSTRING_ARRAY : {
                        HsqlArrayList list = new HsqlArrayList();

                        while (matcher.find()) {
                            list.add(matcher.group());
                        }

                        return list.toArray();
                    }
                }
            }
            case FUNC_CRYPT_KEY : {
                byte[] bytes = Crypto.getNewKey((String) data[0],
                                                (String) data[1]);

                return StringConverter.byteArrayToHexString(bytes);
            }
            case FUNC_LOAD_FILE : {
                String fileName = (String) data[0];

                if (fileName == null) {
                    return null;
                }

                switch (dataType.typeCode) {

                    case Types.SQL_CLOB :
                        return session.sessionData.createClobFromFile(fileName,
                                (String) data[1]);

                    case Types.SQL_BLOB :
                    default :
                        return session.sessionData.createBlobFromFile(
                            fileName);
                }
            }
            case FUNC_LPAD :
            case FUNC_RPAD : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }

                String value;

                if (nodes[0].dataType.typeCode == Types.SQL_CLOB) {
                    value = (String) Type.SQL_VARCHAR.convertToType(session,
                            data[0], nodes[0].dataType);
                } else if (nodes[0].dataType.isCharacterType()) {
                    value = (String) data[0];
                } else {
                    value = nodes[0].dataType.convertToString(data[0]);
                }

                int length = ((Integer) Type.SQL_INTEGER.convertToType(session,
                    data[1], nodes[1].dataType)).intValue();
                String pad = " ";

                if (nodes[2] != null) {
                    pad = nodes[2].dataType.convertToString(data[2]);

                    if (pad.length() == 0) {
                        pad = " ";
                    }
                }

                value = (String) Type.SQL_VARCHAR.trim(session, value, ' ',
                                                       true, true);
                value = StringUtil.toPaddedString(value, length, pad,
                                                  funcType == FUNC_RPAD);

                if (dataType.isLobType()) {
                    return dataType.convertToType(session, value,
                                                  Type.SQL_VARCHAR);
                } else {
                    return value;
                }
            }
            case FUNC_POSITION_ARRAY : {
                if (data[1] == null) {
                    return null;
                }

                if (data[2] == null) {
                    return null;
                }

                Object[]  array       = (Object[]) data[1];
                ArrayType dt          = (ArrayType) nodes[1].dataType;
                Type      elementType = dt.collectionBaseType();
                int start = ((Number) Type.SQL_INTEGER.convertToType(session,
                    data[2], nodes[2].dataType)).intValue();

                if (start <= 0) {
                    throw Error.error(ErrorCode.X_22003);
                }

                start--;

                for (int i = start; i < array.length; i++) {
                    if (elementType.compare(session, data[0], array[i]) == 0) {
                        return ValuePool.getInt(i + 1);
                    }
                }

                return ValuePool.INTEGER_0;
            }
            case FUNC_SORT_ARRAY : {
                if (data[0] == null) {
                    return null;
                }

                ArrayType    dt       = (ArrayType) dataType;
                SortAndSlice exprSort = new SortAndSlice();

                exprSort.prepareSingleColumn(1);

                exprSort.sortDescending[0] = ((Number) data[1]).intValue()
                                             == Tokens.DESC;
                exprSort.sortNullsLast[0] = ((Number) data[2]).intValue()
                                            == Tokens.LAST;

                Object[] array = (Object[]) ArrayUtil.duplicateArray(data[0]);

                dt.sort(session, array, exprSort);

                return array;
            }
            case FUNC_ADD_MONTHS : {
                if (data[0] == null) {
                    return null;
                }

                if (data[1] == null) {
                    return null;
                }

                TimestampData ts     = (TimestampData) data[0];
                int           months = ((Number) data[1]).intValue();

                return Type.SQL_TIMESTAMP_NO_FRACTION.addMonthsSpecial(session,
                        ts, months);
            }
            case FUNC_FROM_TZ : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }

                TimestampData timestamp = (TimestampData) data[0];
                IntervalSecondData zone =
                    (IntervalSecondData) Type.SQL_INTERVAL_HOUR_TO_MINUTE
                        .convertToDefaultType(session, data[1]);

                return new TimestampData(
                    timestamp.getSeconds() - zone.getSeconds(),
                    timestamp.getNanos(), (int) zone.getSeconds());
            }
            case FUNC_LAST_DAY : {
                if (data[0] == null) {
                    return null;
                }

                return Type.SQL_TIMESTAMP_NO_FRACTION.getLastDayOfMonth(
                    session, data[0]);
            }
            case FUNC_MONTHS_BETWEEN : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }

                return DateTimeType.subtractMonthsSpecial(session,
                        (TimestampData) data[0], (TimestampData) data[1]);
            }
            case FUNC_NANVL :
                if (data[0] == null) {
                    return null;
                }

                Double d = Type.SQL_DOUBLE.toDouble(data[0]);

                if (Double.isNaN(d.doubleValue())) {
                    return dataType.convertToType(session, data[1],
                                                  nodes[1].dataType);
                }

                return data[0];

            case FUNC_NEW_TIME : {
                if (data[0] == null || data[1] == null || data[2] == null) {
                    return null;
                }

                IntervalSecondData zone1 =
                    (IntervalSecondData) Type.SQL_INTERVAL_HOUR_TO_MINUTE
                        .convertToDefaultType(session, data[1]);
                IntervalSecondData zone2 =
                    (IntervalSecondData) Type.SQL_INTERVAL_HOUR_TO_MINUTE
                        .convertToDefaultType(session, data[1]);
                Object val =
                    Type.SQL_TIMESTAMP_WITH_TIME_ZONE.changeZone(data[0],
                        Type.SQL_TIMESTAMP, (int) zone2.getSeconds(),
                        (int) zone1.getSeconds());

                return Type.SQL_TIMESTAMP.convertToType(session, val,
                        Type.SQL_TIMESTAMP_WITH_TIME_ZONE);
            }
            case FUNC_NEXT_DAY : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }

                TimestampData date =
                    (TimestampData) dataType.convertToType(session, data[0],
                        nodes[0].dataType);
                int day = DateTimeType.getDayOfWeek((String) data[1]);

                return DateTimeType.nextDayOfWeek(session, date, day);
            }
            case FUNC_NUMTODSINTERVAL : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }

                Object st = Type.SQL_VARCHAR.trim(session, data[1], ' ', true,
                                                  true);

                st = Type.SQL_VARCHAR.upper(session, st);
                st = Type.SQL_VARCHAR.convertToDefaultType(session, st);

                int token    = Tokens.get((String) st);
                int typeCode = IntervalType.getFieldNameTypeForToken(token);

                switch (typeCode) {

                    case Types.SQL_INTERVAL_DAY :
                    case Types.SQL_INTERVAL_HOUR :
                    case Types.SQL_INTERVAL_MINUTE :
                    case Types.SQL_INTERVAL_SECOND :
                        break;

                    default :
                        throw Error.error(ErrorCode.X_42566);
                }

                double value = ((Number) data[0]).doubleValue();

                return IntervalSecondData.newInterval(value, typeCode);
            }
            case FUNC_NUMTOYMINTERVAL : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }

                Object st = Type.SQL_VARCHAR.trim(session, data[1], ' ', true,
                                                  true);

                st = Type.SQL_VARCHAR.upper(session, st);
                st = Type.SQL_VARCHAR.convertToDefaultType(session, st);

                int token    = Tokens.get((String) st);
                int typeCode = IntervalType.getFieldNameTypeForToken(token);

                switch (typeCode) {

                    case Types.SQL_INTERVAL_YEAR :
                    case Types.SQL_INTERVAL_MONTH :
                        break;

                    default :
                        throw Error.error(ErrorCode.X_42566);
                }

                double value = ((Number) data[0]).doubleValue();

                return IntervalMonthData.newInterval(value, typeCode);
            }
            case FUNC_SESSIONTIMEZONE : {
                IntervalSecondData zone =
                    new IntervalSecondData(session.sessionTimeZoneSeconds, 0);

                return Type.SQL_INTERVAL_HOUR_TO_MINUTE.convertToString(zone);
            }
            case FUNC_SYS_EXTRACT_UTC : {
                if (data[0] == null) {
                    return null;
                }

                return Type.SQL_TIMESTAMP_WITH_TIME_ZONE.changeZone(data[0],
                        Type.SQL_TIMESTAMP_WITH_TIME_ZONE, 0, 0);
            }
            case FUNC_SYSDATE : {
                TimestampData timestamp = session.getSystemTimestamp(false);

                return Type.SQL_TIMESTAMP_NO_FRACTION.convertToType(session,
                        timestamp, Type.SQL_TIMESTAMP);

                //
            }
            case FUNC_SYSTIMESTAMP : {
                return session.getSystemTimestamp(true);
            }
            case FUNC_TO_DSINTERVAL : {
                if (data[0] == null) {
                    return null;
                }

                return Type.SQL_INTERVAL_DAY_TO_SECOND.convertToType(session,
                        data[0], Type.SQL_VARCHAR);
            }
            case FUNC_TO_YMINTERVAL : {
                if (data[0] == null) {
                    return null;
                }

                return Type.SQL_INTERVAL_YEAR_TO_MONTH_MAX_PRECISION
                    .convertToType(session, data[0], Type.SQL_VARCHAR);
            }
            case FUNC_TO_TIMESTAMP_TZ : {
                if (data[0] == null || data[1] == null) {
                    return null;
                }
            }
            case FUNC_TRANSLATE : {
                if (data[0] == null || data[1] == null || data[2] == null) {
                    return null;
                }

                IntKeyIntValueHashMap map = charLookup;

                if (map == null) {
                    map = getTranslationMap((String) data[1],
                                            (String) data[2]);
                }

                return translateWithMap((String) data[0], map);
            }
            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "FunctionCustom");
        }
    }

    public void resolveTypes(Session session, Expression parent) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] != null) {
                nodes[i].resolveTypes(session, this);
            }
        }

        switch (funcType) {

            case FUNC_SQLCODE :
                dataType = Type.SQL_INTEGER;

                return;

            case FUNC_SQLERRM :
                dataType = Type.SQL_VARCHAR_DEFAULT;

                return;

            case FUNC_POSITION_CHAR :
            case FUNC_EXTRACT :
            case FUNC_TRIM_CHAR :
            case FUNC_OVERLAY_CHAR :
                super.resolveTypes(session, parent);

                return;

            case FUNC_DATABASE :
                dataType = Type.SQL_VARCHAR_DEFAULT;

                return;

            case FUNC_DATABASE_NAME :
                dataType = Type.SQL_VARCHAR_DEFAULT;

                return;

            case FUNC_ISAUTOCOMMIT :
            case FUNC_ISREADONLYSESSION :
            case FUNC_ISREADONLYDATABASE :
            case FUNC_ISREADONLYDATABASEFILES :
                dataType = Type.SQL_BOOLEAN;

                return;

            case FUNC_ISOLATION_LEVEL :
            case FUNC_SESSION_ISOLATION_LEVEL :
            case FUNC_DATABASE_ISOLATION_LEVEL :
            case FUNC_TRANSACTION_CONTROL :
            case FUNC_DATABASE_VERSION :
                dataType = Type.SQL_VARCHAR_DEFAULT;

                return;

            case FUNC_TIMEZONE :
            case FUNC_SESSION_TIMEZONE :
            case FUNC_DATABASE_TIMEZONE :
                dataType = Type.SQL_INTERVAL_HOUR_TO_MINUTE;

                return;

            case FUNC_SESSION_ID :
            case FUNC_ACTION_ID :
            case FUNC_TRANSACTION_ID :
            case FUNC_TRANSACTION_SIZE :
            case FUNC_LOB_ID :
            case FUNC_IDENTITY :
                dataType = Type.SQL_BIGINT;

                return;

            case FUNC_DIAGNOSTICS : {
                exprSubType = ExpressionColumn.idx_row_count;
                dataType    = Type.SQL_INTEGER;

                return;
            }
            case FUNC_SEQUENCE_ARRAY : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = nodes[1].dataType;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = nodes[0].dataType;
                }

                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_INTEGER;
                    nodes[1].dataType = Type.SQL_INTEGER;
                }

                if (nodes[0].dataType.isNumberType()) {
                    if (nodes[2].dataType == null) {
                        nodes[2].dataType = nodes[0].dataType;
                    }
                } else if (nodes[0].dataType.isDateTimeType()) {
                    if (nodes[2].dataType == null) {
                        throw Error.error(ErrorCode.X_42561);
                    }

                    if (!nodes[2].dataType.isIntervalType()) {
                        throw Error.error(ErrorCode.X_42561);
                    }
                }

                dataType =
                    new ArrayType(nodes[0].getDataType(),
                                  ArrayType.defaultLargeArrayCardinality);

                return;
            }
            case FUNC_DATEADD : {
                int part;

                if (nodes[0].dataType == null) {
                    throw Error.error(ErrorCode.X_42575);
                }

                if (!nodes[0].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                part               = getTSIToken((String) nodes[0].valueData);
                nodes[0].valueData = ValuePool.getInt(part);
                nodes[0].dataType  = Type.SQL_INTEGER;
                funcType           = FUNC_TIMESTAMPADD;

                if (nodes[2].dataType == null) {
                    nodes[2].dataType = Type.SQL_TIMESTAMP;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_BIGINT;
                }

                if (!nodes[1].dataType.isNumberType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                if (!nodes[2].dataType.isDateOrTimestampType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = nodes[2].dataType;

                return;
            }
            case FUNC_TIMESTAMPADD : {
                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_BIGINT;
                }

                if (!nodes[1].dataType.isNumberType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                if (!nodes[2].dataType.isDateOrTimestampType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                if (nodes[2].dataType.typeCode == Types.SQL_DATE) {}

                dataType = nodes[2].dataType;

                if (dataType.typeCode == Types.SQL_DATE) {
                    int part = ((Number) nodes[0].valueData).intValue();

                    switch (part) {

                        case Tokens.NANOSECOND :
                        case Tokens.SQL_TSI_FRAC_SECOND :
                        case Tokens.MICROSECOND :
                        case Tokens.MILLISECOND :
                        case Tokens.SQL_TSI_MILLI_SECOND :
                        case Tokens.SECOND :
                        case Tokens.SQL_TSI_SECOND :
                        case Tokens.MINUTE :
                        case Tokens.SQL_TSI_MINUTE :
                        case Tokens.HOUR :
                        case Tokens.SQL_TSI_HOUR :
                            dataType = Type.SQL_TIMESTAMP;
                    }
                }

                return;
            }
            case FUNC_DATEDIFF : {
                int part;

                if (nodes[2] == null) {
                    nodes[2] = nodes[0];
                    nodes[0] = new ExpressionValue(
                        ValuePool.getInt(Tokens.SQL_TSI_DAY),
                        Type.SQL_INTEGER);
                } else {
                    if (!nodes[0].dataType.isCharacterType()) {
                        throw Error.error(ErrorCode.X_42563);
                    }

                    part = getTSIToken((String) nodes[0].valueData);
                    nodes[0].valueData = ValuePool.getInt(part);
                    nodes[0].dataType  = Type.SQL_INTEGER;
                }

                funcType = FUNC_TIMESTAMPDIFF;
            }

            // fall through
            case FUNC_TIMESTAMPDIFF : {
                if (nodes[1].dataType == null) {
                    nodes[1].dataType = nodes[2].dataType;
                }

                if (nodes[2].dataType == null) {
                    nodes[2].dataType = nodes[1].dataType;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_TIMESTAMP;
                    nodes[2].dataType = Type.SQL_TIMESTAMP;
                }

                if (nodes[1].dataType.isCharacterType()) {
                    nodes[1] =
                        new ExpressionOp(nodes[1],
                                         Type.SQL_TIMESTAMP_WITH_TIME_ZONE);
                }

                if (nodes[2].dataType.isCharacterType()) {
                    nodes[2] =
                        new ExpressionOp(nodes[2],
                                         Type.SQL_TIMESTAMP_WITH_TIME_ZONE);
                }

                switch (nodes[1].dataType.typeCode) {

                    case Types.SQL_DATE :
                        if (nodes[2].dataType.typeCode == Types.SQL_TIME
                                || nodes[2].dataType.typeCode
                                   == Types.SQL_TIME_WITH_TIME_ZONE) {
                            throw Error.error(ErrorCode.X_42563);
                        }

                        switch (((Integer) nodes[0].valueData).intValue()) {

                            case Tokens.SQL_TSI_DAY :
                            case Tokens.SQL_TSI_WEEK :
                            case Tokens.SQL_TSI_MONTH :
                            case Tokens.SQL_TSI_QUARTER :
                            case Tokens.SQL_TSI_YEAR :
                                break;

                            default :
                                throw Error.error(ErrorCode.X_42563);
                        }
                        break;

                    case Types.SQL_TIMESTAMP :
                    case Types.SQL_TIMESTAMP_WITH_TIME_ZONE :
                        if (nodes[2].dataType.typeCode == Types.SQL_TIME
                                || nodes[2].dataType.typeCode
                                   == Types.SQL_TIME_WITH_TIME_ZONE) {
                            throw Error.error(ErrorCode.X_42563);
                        }
                        break;

                    default :
                        throw Error.error(ErrorCode.X_42563);
                }

                dataType = Type.SQL_BIGINT;

                return;
            }
            case FUNC_DATEPART : {

                // only resolve once
                if (extractSpec != 0) {
                    return;
                }

                int part;

                // nodes[0].dataType is constant VARCHAR
                part        = getTSIToken((String) nodes[0].valueData);
                extractSpec = getExtractTokenForTSIPart(part);
                nodes[0] = new ExpressionValue(extractSpec, Type.SQL_INTEGER);
                funcType    = FUNC_EXTRACT;

                super.resolveTypes(session, parent);

                return;
            }
            case FUNC_DATENAME : {

                // only resolve once
                if (extractSpec != 0) {
                    return;
                }

                int part;

                if (nodes[1].dataType == null) {
                    throw Error.error(ErrorCode.X_42567);
                }

                if (!nodes[1].dataType.isDateTimeType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                // nodes[0].dataType is constant VARCHAR
                part        = getTSIToken((String) nodes[0].valueData);
                extractSpec = getExtractTokenForTSIPart(part);

                if (extractSpec == Tokens.DAY_OF_WEEK) {
                    extractSpec = Tokens.DAY_NAME;
                } else if (extractSpec == Tokens.MONTH) {
                    extractSpec = Tokens.MONTH_NAME;
                }

                nodes[0] = new ExpressionValue(extractSpec, Type.SQL_INTEGER);
                dataType = Type.SQL_VARCHAR_DEFAULT;

                return;
            }
            case FUNC_DATE_ADD :
            case FUNC_DATE_SUB : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_DATE;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_INTEGER;
                }

                if (nodes[Expression.LEFT].dataType.isCharacterType()) {
                    nodes[Expression.LEFT] =
                        new ExpressionOp(nodes[Expression.LEFT],
                                         Type.SQL_TIMESTAMP);
                }

                if (nodes[Expression.RIGHT].dataType.isIntegralType()) {
                    nodes[Expression.RIGHT] =
                        new ExpressionOp(nodes[Expression.RIGHT],
                                         Type.SQL_INTERVAL_DAY);
                }

                nodes[0].resolveTypes(session, this);
                nodes[1].resolveTypes(session, this);

                dataType = nodes[0].dataType;

                return;
            }
            case FUNC_DAYS : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_DATE;
                }

                switch (nodes[0].dataType.typeCode) {

                    case Types.SQL_DATE :
                    case Types.SQL_TIMESTAMP :
                    case Types.SQL_TIMESTAMP_WITH_TIME_ZONE :
                        break;

                    default :
                        throw Error.error(ErrorCode.X_42563);
                }

                dataType = Type.SQL_INTEGER;

                return;
            }
            case FUNC_ROUND :
            case FUNC_TRUNC : {
                boolean single = nodes.length == 1 || nodes[1] == null;

                if (nodes[0].dataType == null) {
                    if (single) {
                        if (parent instanceof ExpressionLogical
                                || parent instanceof ExpressionArithmetic) {
                            for (int i = 0; i < parent.nodes.length; i++) {
                                if (parent.nodes[i].dataType != null) {
                                    nodes[0].dataType =
                                        parent.nodes[i].dataType;

                                    break;
                                }
                            }
                        }

                        if (nodes[0].dataType == null) {
                            nodes[0].dataType = Type.SQL_DECIMAL;
                        }

                        if (nodes[0].dataType.isNumberType()) {
                            nodes[0].dataType = Type.SQL_DECIMAL;
                        }
                    } else {
                        if (nodes[1].dataType == null) {
                            nodes[1].dataType = Type.SQL_INTEGER;
                        }

                        if (nodes[1].dataType.isNumberType()) {
                            nodes[0].dataType = Type.SQL_DECIMAL;
                        } else {
                            nodes[0].dataType = Type.SQL_TIMESTAMP;
                        }
                    }
                }

                if (nodes[0].dataType.isDateTimeType()) {
                    if (!single) {
                        if (!nodes[1].dataType.isCharacterType()) {
                            throw Error.error(ErrorCode.X_42566);
                        }
                    }

                    dataType = nodes[0].dataType;

                    break;
                } else if (nodes[0].dataType.isNumberType()) {

                    //
                } else {
                    throw Error.error(ErrorCode.X_42561);
                }
            }

            // fall through
            case FUNC_TRUNCATE : {
                Number offset = null;

                if (nodes[0].dataType == null) {
                    throw Error.error(ErrorCode.X_42567);
                }

                if (!nodes[0].dataType.isNumberType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                if (nodes[1] == null) {
                    nodes[1] = new ExpressionValue(ValuePool.INTEGER_0,
                                                   Type.SQL_INTEGER);
                    offset = ValuePool.INTEGER_0;
                } else {
                    if (nodes[1].dataType == null) {
                        nodes[1].dataType = Type.SQL_INTEGER;
                    } else if (!nodes[1].dataType.isIntegralType()) {
                        throw Error.error(ErrorCode.X_42563);
                    }

                    if (nodes[1].opType == OpTypes.VALUE) {
                        offset = (Number) nodes[1].getValue(session);
                    }
                }

                dataType = nodes[0].dataType;

                if (offset != null) {
                    int scale = offset.intValue();

                    if (scale < 0) {
                        scale = 0;
                    } else if (scale > dataType.scale) {
                        scale = dataType.scale;
                    }

                    if (dataType.typeCode == Types.SQL_DECIMAL
                            || dataType.typeCode == Types.SQL_NUMERIC) {
                        if (scale != dataType.scale) {
                            dataType = new NumberType(dataType.typeCode,
                                                      dataType.precision
                                                      - dataType.scale
                                                      + scale, scale);
                        }
                    }
                }

                return;
            }
            case FUNC_TO_CHAR : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_TIMESTAMP;
                }

                if (nodes[1] != null) {
                    if (nodes[1].dataType == null) {
                        nodes[1].dataType = Type.SQL_VARCHAR_DEFAULT;
                    }

                    if (!nodes[1].dataType.isCharacterType()) {
                        throw Error.error(ErrorCode.X_42563);
                    }

                    if (!nodes[0].dataType.isDateTimeType()) {
                        throw Error.error(ErrorCode.X_42563);
                    }
                }

                // fixed maximum as format is a variable
                dataType = CharacterType.getCharacterType(Types.SQL_VARCHAR,
                        64);

                return;
            }
            case FUNC_TO_NUMBER : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                /*
                                if (nodes[1] != null) {
                                    if (nodes[1].dataType == null) {
                 nodes[1].dataType = Type.SQL_VARCHAR_DEFAULT;
                                    }

                                    if (!nodes[1].dataType.isCharacterType()) {
                                        throw Error.error(ErrorCode.X_42563);
                                    }
                                }
                 */
                if (!nodes[0].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                // fixed maximum as format is a variable
                dataType = Type.SQL_DECIMAL_DEFAULT;

                return;
            }
            case FUNC_TO_DATE :
            case FUNC_TO_TIMESTAMP : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                if (nodes[1] == null) {
                    String format = "DD-MON-YYYY HH24:MI:SS";

                    if (funcType == FUNC_TO_TIMESTAMP) {
                        format = "DD-MON-YYYY HH24:MI:SS.FF";
                    }

                    nodes[1] = new ExpressionValue(format, Type.SQL_VARCHAR);
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                if (!nodes[0].dataType.isCharacterType()
                        && !nodes[0].dataType.isDateOrTimestampType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                if (!nodes[1].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                dataType = funcType == FUNC_TO_DATE
                           ? Type.SQL_TIMESTAMP_NO_FRACTION
                           : Type.SQL_TIMESTAMP;

                return;
            }
            case FUNC_TIMESTAMP : {
                Type argType = nodes[0].dataType;

                if (nodes[1] == null) {
                    if (argType == null) {
                        argType = nodes[0].dataType = Type.SQL_VARCHAR_DEFAULT;
                    }

                    if (argType.isCharacterType()
                            || argType.typeCode == Types.SQL_TIMESTAMP
                            || argType.typeCode
                               == Types.SQL_TIMESTAMP_WITH_TIME_ZONE) {}
                    else if (argType.isNumberType()) {}
                    else {
                        throw Error.error(ErrorCode.X_42561);
                    }
                } else {
                    if (argType == null) {
                        if (nodes[1].dataType == null) {
                            argType = nodes[0].dataType = nodes[1].dataType =
                                Type.SQL_VARCHAR_DEFAULT;
                        } else {
                            if (nodes[1].dataType.isCharacterType()) {
                                argType = nodes[0].dataType =
                                    Type.SQL_VARCHAR_DEFAULT;
                            } else {
                                argType = nodes[0].dataType = Type.SQL_DATE;
                            }
                        }
                    }

                    if (nodes[1].dataType == null) {
                        if (argType.isCharacterType()) {
                            nodes[1].dataType = Type.SQL_VARCHAR_DEFAULT;
                        } else if (argType.typeCode == Types.SQL_DATE) {
                            nodes[1].dataType = Type.SQL_TIME;
                        }
                    }

                    if ((argType.typeCode == Types.SQL_DATE && nodes[1]
                            .dataType.typeCode == Types.SQL_TIME) || argType
                                .isCharacterType() && nodes[1].dataType
                                .isCharacterType()) {}
                    else {
                        throw Error.error(ErrorCode.X_42561);
                    }
                }

                dataType = Type.SQL_TIMESTAMP;

                return;
            }
            case FUNC_TIMESTAMP_WITH_ZONE : {
                Type argType = nodes[0].dataType;

                if (argType == null) {
                    argType = nodes[0].dataType = Type.SQL_BIGINT;
                }

                if (argType.typeCode == Types.SQL_TIMESTAMP
                        || argType.typeCode
                           == Types.SQL_TIMESTAMP_WITH_TIME_ZONE) {}
                else if (argType.isNumberType()) {}
                else {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = Type.SQL_TIMESTAMP_WITH_TIME_ZONE;

                return;
            }
            case FUNC_PI :
                dataType = Type.SQL_DOUBLE;
                break;

            case FUNC_UUID : {
                if (nodes[0] == null) {
                    dataType = Type.SQL_BINARY_16;
                } else {
                    if (nodes[0].dataType == null) {
                        nodes[0].dataType = Type.SQL_VARCHAR;
                        dataType          = Type.SQL_BINARY_16;
                    } else if (nodes[0].dataType.isCharacterType()) {
                        dataType = Type.SQL_BINARY_16;
                    } else if (nodes[0].dataType.isBinaryType()
                               && !nodes[0].dataType.isLobType()) {
                        dataType = Type.SQL_CHAR_UUID;
                    } else {
                        throw Error.error(ErrorCode.X_42563);
                    }
                }

                break;
            }
            case FUNC_UNIX_MILLIS :
            case FUNC_UNIX_TIMESTAMP : {
                if (nodes[0] != null) {
                    if (nodes[0].dataType == null) {
                        nodes[0].dataType = Type.SQL_TIMESTAMP;
                    } else if (!nodes[0].dataType.isDateTimeType()
                               || nodes[0].dataType.typeCode == Types.SQL_TIME
                               || nodes[0].dataType.typeCode
                                  == Types.SQL_TIME_WITH_TIME_ZONE) {
                        throw Error.error(ErrorCode.X_42563);
                    }
                }

                dataType = Type.SQL_BIGINT;

                break;
            }
            case FUNC_RAND : {
                if (nodes[0] != null) {
                    if (nodes[0].dataType == null) {
                        nodes[0].dataType = Type.SQL_BIGINT;
                    } else if (!nodes[0].dataType.isExactNumberType()) {
                        throw Error.error(ErrorCode.X_42563);
                    }
                }

                dataType = Type.SQL_DOUBLE;

                break;
            }
            case FUNC_ACOS :
            case FUNC_ASIN :
            case FUNC_ATAN :
            case FUNC_COS :
            case FUNC_COSH :
            case FUNC_COT :
            case FUNC_DEGREES :
            case FUNC_SIN :
            case FUNC_SINH :
            case FUNC_TAN :
            case FUNC_TANH :
            case FUNC_LOG10 :
            case FUNC_RADIANS :
            case FUNC_ROUNDMAGIC : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_DOUBLE;
                }

                if (!nodes[0].dataType.isNumberType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = Type.SQL_DOUBLE;

                break;
            }
            case FUNC_SIGN : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_DOUBLE;
                }

                if (!nodes[0].dataType.isNumberType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = Type.SQL_INTEGER;

                break;
            }
            case FUNC_ATAN2 : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_DOUBLE;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_DOUBLE;
                }

                if (!nodes[0].dataType.isNumberType()
                        || !nodes[1].dataType.isNumberType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = Type.SQL_DOUBLE;

                break;
            }
            case FUNC_SOUNDEX : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR;
                }

                if (!nodes[0].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = CharacterType.getCharacterType(Types.SQL_VARCHAR,
                        4);

                break;
            }
            case FUNC_BITAND :
            case FUNC_BITANDNOT :
            case FUNC_BITNOT :
            case FUNC_BITOR :
            case FUNC_BITXOR : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = nodes[1].dataType;
                }

                if (funcType == FUNC_BITNOT) {
                    if (nodes[0].dataType == null) {
                        nodes[0].dataType = Type.SQL_INTEGER;
                    }

                    dataType = nodes[0].dataType;
                } else {
                    dataType = nodes[0].dataType;

                    if (nodes[1].dataType == null) {
                        nodes[1].dataType = nodes[0].dataType;
                    }

                    for (int i = 0; i < nodes.length; i++) {
                        if (nodes[i].dataType == null) {
                            nodes[i].dataType = Type.SQL_INTEGER;
                        }
                    }

                    dataType =
                        nodes[0].dataType.getAggregateType(nodes[1].dataType);
                }

                switch (dataType.typeCode) {

                    case Types.SQL_DOUBLE :
                    case Types.SQL_DECIMAL :
                    case Types.SQL_NUMERIC :
                    case Types.SQL_BIGINT :
                    case Types.SQL_INTEGER :
                    case Types.SQL_SMALLINT :
                    case Types.TINYINT :
                        break;

                    case Types.SQL_BIT :
                    case Types.SQL_BIT_VARYING :
                        break;

                    default :
                        throw Error.error(ErrorCode.X_42561);
                }

                break;
            }
            case FUNC_ASCII : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR;
                }

                if (!nodes[0].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = Type.SQL_INTEGER;

                break;
            }
            case FUNC_CHAR : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_INTEGER;
                }

                if (!nodes[0].dataType.isExactNumberType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = CharacterType.getCharacterType(Types.SQL_VARCHAR,
                        1);

                break;
            }
            case FUNC_DIFFERENCE : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_VARCHAR;
                }

                dataType = Type.SQL_INTEGER;

                break;
            }
            case FUNC_FROM_BASE64 : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR;
                }

                if (!nodes[0].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = nodes[0].dataType.precision == 0
                           ? Type.SQL_VARBINARY_DEFAULT
                           : BinaryType.getBinaryType(
                               Types.SQL_VARBINARY,
                               (nodes[0].dataType.precision * 3) / 4);

                break;
            }
            case FUNC_UNHEX :
            case FUNC_HEXTORAW : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR;
                }

                if (!nodes[0].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = nodes[0].dataType.precision == 0
                           ? Type.SQL_VARBINARY_DEFAULT
                           : BinaryType.getBinaryType(
                               Types.SQL_VARBINARY,
                               nodes[0].dataType.precision / 2);

                break;
            }
            case FUNC_TO_BASE64 : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARBINARY;
                }

                if (!nodes[0].dataType.isBinaryType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                if (nodes[0].dataType.precision == 0) {
                    dataType = Type.SQL_VARCHAR_DEFAULT;
                } else {
                    long precision = ((nodes[0].dataType.precision + 2) / 3)
                                     * 4;

                    dataType =
                        CharacterType.getCharacterType(Types.SQL_VARCHAR,
                                                       precision);
                }

                break;
            }
            case FUNC_HEX : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARBINARY;
                }

                if (nodes[0].dataType.isNumberType()) {
                    dataType = Type.SQL_VARCHAR_DEFAULT;

                    break;
                }

                dataType = nodes[0].dataType.precision == 0
                           ? Type.SQL_VARCHAR_DEFAULT
                           : CharacterType.getCharacterType(Types.SQL_VARCHAR,
                           nodes[0].dataType.precision * 2);

                break;
            }
            case FUNC_RAWTOHEX : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARBINARY;
                }

                if (!nodes[0].dataType.isBinaryType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = nodes[0].dataType.precision == 0
                           ? Type.SQL_VARCHAR_DEFAULT
                           : CharacterType.getCharacterType(Types.SQL_VARCHAR,
                           nodes[0].dataType.precision * 2);

                break;
            }
            case FUNC_REPEAT : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR;
                }

                boolean isChar = nodes[0].dataType.isCharacterType();

                if (!isChar && !nodes[0].dataType.isBinaryType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                if (!nodes[1].dataType.isExactNumberType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = isChar ? Type.SQL_VARCHAR_DEFAULT
                                  : Type.SQL_VARBINARY_DEFAULT;

                break;
            }
            case FUNC_REPLACE : {
                if (nodes[2] == null) {
                    nodes[2] = new ExpressionValue("", Type.SQL_VARCHAR);
                }

                for (int i = 0; i < nodes.length; i++) {
                    if (nodes[i].dataType == null) {
                        nodes[i].dataType = Type.SQL_VARCHAR;
                    } else if (!nodes[i].dataType.isCharacterType()) {
                        throw Error.error(ErrorCode.X_42561);
                    }
                }

                dataType = Type.SQL_VARCHAR_DEFAULT;

                break;
            }
            case FUNC_LEFT :
            case FUNC_RIGHT : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR;
                }

                if (!nodes[0].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_INTEGER;
                }

                if (!nodes[1].dataType.isExactNumberType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = nodes[0].dataType.precision == 0
                           ? Type.SQL_VARCHAR_DEFAULT
                           : ((CharacterType) nodes[0].dataType)
                               .getCharacterType(nodes[0].dataType.precision);

                break;
            }
            case FUNC_SPACE : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_INTEGER;
                }

                if (!nodes[0].dataType.isIntegralType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                dataType = Type.SQL_VARCHAR_DEFAULT;

                break;
            }
            case FUNC_REVERSE : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                dataType = nodes[0].dataType;

                if (!dataType.isCharacterType() || dataType.isLobType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                break;
            }
            case FUNC_REGEXP_REPLACE :
                if (nodes[2] == null) {
                    nodes[2] = new ExpressionValue("", Type.SQL_VARCHAR);
                }
            case FUNC_REGEXP_MATCHES :
            case FUNC_REGEXP_SUBSTRING :
            case FUNC_REGEXP_SUBSTRING_ARRAY : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                if (!nodes[0].dataType.isCharacterType()
                        || !nodes[1].dataType.isCharacterType()
                        || nodes[1].dataType.isLobType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                if (nodes[1].exprSubType == OpTypes.VALUE) {
                    String matchPattern = (String) nodes[1].getValue(session);

                    pattern = Pattern.compile(matchPattern);
                }

                switch (funcType) {

                    case FUNC_REGEXP_MATCHES :
                        dataType = Type.SQL_BOOLEAN;
                        break;

                    case FUNC_REGEXP_REPLACE :
                        dataType = Type.SQL_VARCHAR_DEFAULT;
                        break;

                    case FUNC_REGEXP_SUBSTRING :
                        dataType = Type.SQL_VARCHAR_DEFAULT;
                        break;

                    case FUNC_REGEXP_SUBSTRING_ARRAY :
                        dataType = Type.getDefaultArrayType(Types.SQL_VARCHAR);
                        break;

                    default :
                }

                break;
            }
            case FUNC_CRYPT_KEY : {
                for (int i = 0; i < nodes.length; i++) {
                    if (nodes[i].dataType == null) {
                        nodes[i].dataType = Type.SQL_VARCHAR;
                    } else if (!nodes[i].dataType.isCharacterType()) {
                        throw Error.error(ErrorCode.X_42561);
                    }
                }

                dataType = Type.SQL_VARCHAR_DEFAULT;

                break;
            }
            case FUNC_LOAD_FILE : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                if (!nodes[0].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                if (nodes[1] == null) {
                    dataType = Type.SQL_BLOB;
                } else {
                    dataType = Type.SQL_CLOB;

                    if (nodes[1].dataType == null
                            || !nodes[1].dataType.isCharacterType()) {
                        throw Error.error(ErrorCode.X_42561);
                    }
                }

                break;
            }
            case FUNC_LPAD :
            case FUNC_RPAD : {
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_INTEGER;
                }

                if (!nodes[1].dataType.isIntegralType()) {
                    throw Error.error(ErrorCode.X_42561);
                }

                if (nodes[2] != null) {
                    if (nodes[2].dataType == null) {
                        nodes[2].dataType = Type.SQL_VARCHAR_DEFAULT;
                    }

                    if (!nodes[2].dataType.isCharacterType()) {
                        throw Error.error(ErrorCode.X_42561);
                    }
                }

                dataType = nodes[0].dataType;

                if (dataType.typeCode != Types.SQL_CLOB) {
                    dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                if (nodes[1].opType == OpTypes.VALUE) {
                    Number value = (Number) nodes[1].getValue(session);

                    if (value != null) {
                        dataType = ((CharacterType) dataType).getCharacterType(
                            value.longValue());
                    }
                }

                break;
            }
            case FUNC_POSITION_ARRAY : {
                if (nodes[1].dataType == null) {
                    throw Error.error(ErrorCode.X_42567);
                }

                if (!nodes[1].dataType.isArrayType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                if (nodes[0].dataType == null) {
                    nodes[0].dataType = nodes[1].dataType.collectionBaseType();
                }

                if (!nodes[1].dataType.collectionBaseType().canCompareDirect(
                        nodes[0].dataType)) {
                    throw Error.error(ErrorCode.X_42563);
                }

                if (nodes[2] == null) {
                    nodes[2] = new ExpressionValue(ValuePool.INTEGER_1,
                                                   Type.SQL_INTEGER);
                }

                if (nodes[2].dataType == null) {
                    nodes[2].dataType = Type.SQL_INTEGER;
                }

                if (!nodes[2].dataType.isIntegralType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                dataType = Type.SQL_INTEGER;

                break;
            }
            case FUNC_SORT_ARRAY : {
                if (nodes[0].dataType == null) {
                    throw Error.error(ErrorCode.X_42567);
                }

                if (!nodes[0].dataType.isArrayType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                if (nodes[1] == null) {
                    nodes[1] =
                        new ExpressionValue(ValuePool.getInt(Tokens.ASC),
                                            Type.SQL_INTEGER);
                }

                if (nodes[2] == null) {
                    nodes[2] =
                        new ExpressionValue(ValuePool.getInt(Tokens.FIRST),
                                            Type.SQL_INTEGER);
                }

                dataType = nodes[0].dataType;

                break;
            }
            case FUNC_ADD_MONTHS :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                }

                if (!nodes[0].dataType.isDateOrTimestampType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                break;

            case FUNC_DBTIMEZONE :
                dataType = CharacterType.getCharacterType(Types.SQL_VARCHAR,
                        6);
                break;

            case FUNC_FROM_TZ :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_TIMESTAMP;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_VARCHAR;
                }

                dataType = Type.SQL_TIMESTAMP_WITH_TIME_ZONE;
                break;

            case FUNC_LAST_DAY :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                }

                if (nodes[0].dataType.isCharacterType()) {
                    nodes[0] =
                        new ExpressionOp(nodes[0],
                                         Type.SQL_TIMESTAMP_NO_FRACTION);
                }

                if (!nodes[0].dataType.isDateOrTimestampType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                break;

            case FUNC_MONTHS_BETWEEN :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                }

                if (!nodes[0].dataType.isDateOrTimestampType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                if (!nodes[1].dataType.isDateOrTimestampType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                dataType = Type.SQL_DECIMAL_DEFAULT;
                break;

            case FUNC_NANVL :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_DOUBLE;
                }

                if (nodes[0].dataType.isCharacterType()) {
                    nodes[0] = new ExpressionOp(nodes[0], Type.SQL_DOUBLE);
                }

                dataType = nodes[0].dataType;
            case FUNC_NEW_TIME :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_VARCHAR;
                }

                if (nodes[2].dataType == null) {
                    nodes[2].dataType = Type.SQL_VARCHAR;
                }

                dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                break;

            case FUNC_NEXT_DAY :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_VARCHAR;
                }

                dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                break;

            case FUNC_NUMTODSINTERVAL :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_DOUBLE;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_VARCHAR;
                }

                if (!nodes[0].dataType.isNumberType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                if (!nodes[1].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                dataType = Type.SQL_INTERVAL_DAY_TO_SECOND_MAX_PRECISION;
                break;

            case FUNC_NUMTOYMINTERVAL :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_DOUBLE;
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_VARCHAR;
                }

                if (!nodes[0].dataType.isNumberType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                if (!nodes[1].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                dataType = Type.SQL_INTERVAL_YEAR_TO_MONTH_MAX_PRECISION;
                break;

            case FUNC_SESSIONTIMEZONE :
                dataType = CharacterType.getCharacterType(Types.SQL_VARCHAR,
                        6);
                break;

            case FUNC_SYS_EXTRACT_UTC :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_TIMESTAMP_WITH_TIME_ZONE;
                }

                dataType = Type.SQL_TIMESTAMP;
                break;

            case FUNC_SYSDATE :
                dataType = Type.SQL_TIMESTAMP_NO_FRACTION;
                break;

            case FUNC_SYSTIMESTAMP :
                dataType = Type.SQL_TIMESTAMP_WITH_TIME_ZONE;
                break;

            case FUNC_TO_DSINTERVAL :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR;
                }

                dataType = Type.SQL_INTERVAL_DAY_TO_SECOND_MAX_PRECISION;
                break;

            case FUNC_TO_YMINTERVAL :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR;
                }

                dataType = Type.SQL_INTERVAL_YEAR_TO_MONTH_MAX_PRECISION;
                break;

            case FUNC_TO_TIMESTAMP_TZ :
                if (nodes[0].dataType == null) {
                    nodes[0].dataType = Type.SQL_VARCHAR_DEFAULT;
                }

                if (nodes[1] == null) {
                    String format = "DD-MON-YYYY HH24:MI:SS:FF TZH:TZM";

                    nodes[1] = new ExpressionValue(format, Type.SQL_VARCHAR);
                }

                if (nodes[1].dataType == null) {
                    nodes[1].dataType = Type.SQL_VARCHAR;
                }

                if (!nodes[0].dataType.isCharacterType()
                        || !nodes[1].dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42567);
                }

                dataType = Type.SQL_TIMESTAMP_WITH_TIME_ZONE;
                break;

            case FUNC_TRANSLATE :
                for (int i = 0; i < nodes.length; i++) {
                    if (nodes[i].dataType == null) {
                        nodes[i].dataType = Type.SQL_VARCHAR_DEFAULT;
                    }

                    if (!nodes[i].dataType.isCharacterType()
                            || nodes[i].dataType.isLobType()) {
                        throw Error.error(ErrorCode.X_42563);
                    }
                }

                if (nodes[1].valueData != null && nodes[2].valueData != null) {
                    this.charLookup =
                        getTranslationMap((String) nodes[1].valueData,
                                          (String) nodes[2].valueData);
                }

                dataType = nodes[0].dataType;
                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "FunctionCustom");
        }
    }

    public String getSQL() {

        switch (funcType) {

            case FUNC_POSITION_CHAR : {

                // LOCATE
                StringBuffer sb = new StringBuffer(Tokens.T_LOCATE).append(
                    Tokens.T_OPENBRACKET).append(nodes[0].getSQL()).append(
                    Tokens.T_COMMA).append(nodes[1].getSQL());

                if (nodes.length > 3 && nodes[3] != null) {
                    sb.append(Tokens.T_COMMA).append(nodes[3].getSQL());
                }

                sb.append(Tokens.T_CLOSEBRACKET).toString();

                return sb.toString();
            }
            case FUNC_LPAD :
            case FUNC_RPAD : {
                StringBuffer sb = new StringBuffer(name);

                sb.append(Tokens.T_OPENBRACKET).append(nodes[0].getSQL());
                sb.append(Tokens.T_COMMA).append(nodes[1].getSQL());

                if (nodes[2] != null) {
                    sb.append(Tokens.T_COMMA).append(nodes[2].getSQL());
                }

                sb.append(Tokens.T_CLOSEBRACKET).toString();

                return sb.toString();
            }
            case FUNC_EXTRACT :
            case FUNC_TRIM_CHAR :
            case FUNC_OVERLAY_CHAR :
                return super.getSQL();

            case FUNC_POSITION_ARRAY : {
                StringBuffer sb = new StringBuffer(name).append('(');

                sb.append(nodes[0].getSQL()).append(' ').append(Tokens.T_IN);
                sb.append(' ').append(nodes[1].getSQL());

                if (((Number) nodes[1].valueData).intValue() == Tokens.DESC) {
                    sb.append(' ').append(Tokens.T_FROM);
                    sb.append(' ').append(nodes[2].getSQL());
                }

                sb.append(')');

                return sb.toString();
            }
            case FUNC_SORT_ARRAY : {
                StringBuffer sb = new StringBuffer(name).append('(');

                sb.append(nodes[0].getSQL());

                if (((Number) nodes[1].valueData).intValue() == Tokens.DESC) {
                    sb.append(' ').append(Tokens.T_DESC);
                }

                if (((Number) nodes[2].valueData).intValue() == Tokens.LAST) {
                    sb.append(' ').append(Tokens.T_NULLS).append(' ');
                    sb.append(Tokens.T_LAST);
                }

                sb.append(')');

                return sb.toString();
            }
            case FUNC_SYSDATE :
            case FUNC_SYSTIMESTAMP :
                return name;

            case FUNC_DATABASE :
            case FUNC_DATABASE_NAME :
            case FUNC_ISAUTOCOMMIT :
            case FUNC_ISREADONLYSESSION :
            case FUNC_ISREADONLYDATABASE :
            case FUNC_ISREADONLYDATABASEFILES :
            case FUNC_ISOLATION_LEVEL :
            case FUNC_SESSION_ISOLATION_LEVEL :
            case FUNC_DATABASE_ISOLATION_LEVEL :
            case FUNC_TRANSACTION_CONTROL :
            case FUNC_TIMEZONE :
            case FUNC_SESSION_TIMEZONE :
            case FUNC_DATABASE_TIMEZONE :
            case FUNC_DATABASE_VERSION :
            case FUNC_PI :
            case FUNC_IDENTITY :
            case FUNC_SESSION_ID :
            case FUNC_ACTION_ID :
            case FUNC_TRANSACTION_ID :
            case FUNC_TRANSACTION_SIZE :
                return new StringBuffer(name).append(
                    Tokens.T_OPENBRACKET).append(
                    Tokens.T_CLOSEBRACKET).toString();

            case FUNC_TIMESTAMPADD : {
                String token = Tokens.getSQLTSIString(
                    ((Number) nodes[0].getValue(null)).intValue());

                return new StringBuffer(Tokens.T_TIMESTAMPADD).append(
                    Tokens.T_OPENBRACKET).append(token).append(
                    Tokens.T_COMMA).append(nodes[1].getSQL()).append(
                    Tokens.T_COMMA).append(nodes[2].getSQL()).append(
                    Tokens.T_CLOSEBRACKET).toString();
            }
            case FUNC_TIMESTAMPDIFF : {
                String token = Tokens.getSQLTSIString(
                    ((Number) nodes[0].getValue(null)).intValue());

                return new StringBuffer(Tokens.T_TIMESTAMPDIFF).append(
                    Tokens.T_OPENBRACKET).append(token).append(
                    Tokens.T_COMMA).append(nodes[1].getSQL()).append(
                    Tokens.T_COMMA).append(nodes[2].getSQL()).append(
                    Tokens.T_CLOSEBRACKET).toString();
            }
            case FUNC_DATE_ADD :
                return new StringBuffer(nodes[0].getSQL()).append(' ').append(
                    '+').append(nodes[1].getSQL()).toString();

            case FUNC_DATE_SUB :
                return new StringBuffer(nodes[0].getSQL()).append(' ').append(
                    '-').append(nodes[1].getSQL()).toString();

            case FUNC_UNIX_MILLIS :
            case FUNC_UNIX_TIMESTAMP :
            case FUNC_UUID :
            case FUNC_RAND : {
                StringBuffer sb = new StringBuffer(name).append('(');

                if (nodes[0] != null) {
                    sb.append(nodes[0].getSQL());
                }

                sb.append(')');

                return sb.toString();
            }
            case FUNC_LOAD_FILE :
            case FUNC_ROUND :
            case FUNC_TIMESTAMP :
            case FUNC_TO_DATE :
            case FUNC_TO_NUMBER :
            case FUNC_TO_TIMESTAMP :
            case FUNC_TO_TIMESTAMP_TZ :
            case FUNC_TRUNC :
            case FUNC_TRUNCATE :
            case FUNC_TO_CHAR : {
                StringBuffer sb = new StringBuffer(name).append('(');

                sb.append(nodes[0].getSQL());

                if (nodes.length > 1 && nodes[1] != null) {
                    sb.append(',').append(nodes[1].getSQL());
                }

                sb.append(')');

                return sb.toString();
            }
            case FUNC_ACOS :
            case FUNC_ASCII :
            case FUNC_ASIN :
            case FUNC_ATAN :
            case FUNC_CHAR :
            case FUNC_COS :
            case FUNC_COSH :
            case FUNC_COT :
            case FUNC_DAYS :
            case FUNC_DEGREES :
            case FUNC_SIN :
            case FUNC_SINH :
            case FUNC_TAN :
            case FUNC_TANH :
            case FUNC_LOG10 :
            case FUNC_RADIANS :
            case FUNC_ROUNDMAGIC :
            case FUNC_SIGN :
            case FUNC_SOUNDEX :
            case FUNC_SPACE :
            case FUNC_REVERSE :
            case FUNC_HEX :
            case FUNC_UNHEX :
            case FUNC_FROM_BASE64 :
            case FUNC_TO_BASE64 :
            case FUNC_HEXTORAW :
            case FUNC_RAWTOHEX :
            case FUNC_LOB_ID : {
                return new StringBuffer(name).append('(').append(
                    nodes[0].getSQL()).append(')').toString();
            }
            case FUNC_ATAN2 :
            case FUNC_BITAND :
            case FUNC_BITANDNOT :
            case FUNC_BITOR :
            case FUNC_BITNOT :
            case FUNC_BITXOR :
            case FUNC_CRYPT_KEY :
            case FUNC_DIFFERENCE :
            case FUNC_LEFT :
            case FUNC_RIGHT :
            case FUNC_REGEXP_MATCHES :
            case FUNC_REGEXP_SUBSTRING :
            case FUNC_REGEXP_SUBSTRING_ARRAY :
            case FUNC_REPEAT : {
                return new StringBuffer(name).append('(').append(
                    nodes[0].getSQL()).append(Tokens.T_COMMA).append(
                    nodes[1].getSQL()).append(')').toString();
            }
            case FUNC_DIAGNOSTICS : {
                StringBuffer sb = new StringBuffer(name).append('(');

                //exprSubType == ExpressionColumn.idx_row_count
                sb.append(Tokens.T_ROW_COUNT);
                sb.append(')');

                return sb.toString();
            }
            case FUNC_SEQUENCE_ARRAY :
            case FUNC_REGEXP_REPLACE :
            case FUNC_REPLACE : {
                return new StringBuffer(name).append('(').append(
                    nodes[0].getSQL()).append(Tokens.T_COMMA).append(
                    nodes[1].getSQL()).append(Tokens.T_COMMA).append(
                    nodes[2].getSQL()).append(')').toString();
            }
            case FUNC_ADD_MONTHS :
            case FUNC_DATENAME :
            case FUNC_DATEPART :
            case FUNC_DBTIMEZONE :
            case FUNC_FROM_TZ :
            case FUNC_LAST_DAY :
            case FUNC_MONTHS_BETWEEN :
            case FUNC_NEXT_DAY :
            case FUNC_NANVL :
            case FUNC_NEW_TIME :
            case FUNC_NUMTODSINTERVAL :
            case FUNC_NUMTOYMINTERVAL :
            case FUNC_SESSIONTIMEZONE :
            case FUNC_SYS_EXTRACT_UTC :
            case FUNC_TO_DSINTERVAL :
            case FUNC_TO_YMINTERVAL :
            case FUNC_TRANSLATE :
                return getSQLSimple();

            default :
                return super.getSQL();
        }
    }

    private String getSQLSimple() {

        StringBuffer sb = new StringBuffer(name).append('(');

        for (int i = 0; i < nodes.length; i++) {
            if (i > 0) {
                sb.append(',');
            }

            sb.append(nodes[i].getSQL());
        }

        sb.append(')');

        return sb.toString();
    }

    /**
     * Returns a four character code representing the sound of the given
     * <code>String</code>. Non-ASCCI characters in the
     * input <code>String</code> are ignored. <p>
     *
     * This method was rewritten for HSQLDB to comply with the description at
     * <a href="http://www.archives.gov/research/census/soundex.html">
     * http://www.archives.gov/research/census/soundex.html </a>.<p>
     * @param s the <code>String</code> for which to calculate the 4 character
     *      <code>SOUNDEX</code> value
     * @return the 4 character <code>SOUNDEX</code> value for the given
     *      <code>String</code>
     */
    private static char[] soundex(String s) {

        if (s == null) {
            return null;
        }

        s = s.toUpperCase(Locale.ENGLISH);

        int    len       = s.length();
        char[] b         = new char[] {
            '0', '0', '0', '0'
        };
        char   lastdigit = '0';

        for (int i = 0, j = 0; i < len && j < 4; i++) {
            char c = s.charAt(i);
            char newdigit;

            if ("AEIOUY".indexOf(c) != -1) {
                newdigit = '7';
            } else if (c == 'H' || c == 'W') {
                newdigit = '8';
            } else if ("BFPV".indexOf(c) != -1) {
                newdigit = '1';
            } else if ("CGJKQSXZ".indexOf(c) != -1) {
                newdigit = '2';
            } else if (c == 'D' || c == 'T') {
                newdigit = '3';
            } else if (c == 'L') {
                newdigit = '4';
            } else if (c == 'M' || c == 'N') {
                newdigit = '5';
            } else if (c == 'R') {
                newdigit = '6';
            } else {
                continue;
            }

            if (j == 0) {
                b[j++]    = c;
                lastdigit = newdigit;
            } else if (newdigit <= '6') {
                if (newdigit != lastdigit) {
                    b[j++]    = newdigit;
                    lastdigit = newdigit;
                }
            } else if (newdigit == '7') {
                lastdigit = newdigit;
            }
        }

        return b;
    }

    private static int getTSIToken(String string) {

        int part;

        if ("yy".equalsIgnoreCase(string) || "yyyy".equalsIgnoreCase(string)
                || "year".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_YEAR;
        } else if ("qq".equalsIgnoreCase(string)
                   || "quarter".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_QUARTER;
        } else if ("mm".equalsIgnoreCase(string)
                   || "month".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_MONTH;
        } else if ("dd".equalsIgnoreCase(string)
                   || "day".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_DAY;
        } else if ("dy".equalsIgnoreCase(string)
                   || "dayofyear".equalsIgnoreCase(string)) {
            part = Tokens.DAY_OF_YEAR;
        } else if ("dw".equalsIgnoreCase(string)
                   || "weekday".equalsIgnoreCase(string)) {
            part = Tokens.DAY_OF_WEEK;
        } else if ("wk".equalsIgnoreCase(string)
                   || "week".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_WEEK;
        } else if ("hh".equalsIgnoreCase(string)
                   || "hour".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_HOUR;
        } else if ("mi".equalsIgnoreCase(string)
                   || "minute".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_MINUTE;
        } else if ("ss".equalsIgnoreCase(string)
                   || "second".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_SECOND;
        } else if ("ms".equalsIgnoreCase(string)
                   || "millisecond".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_MILLI_SECOND;
        } else if ("mcs".equalsIgnoreCase(string)
                   || "microsecond".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_MICRO_SECOND;
        } else if ("ns".equalsIgnoreCase(string)
                   || "nanosecond".equalsIgnoreCase(string)) {
            part = Tokens.SQL_TSI_FRAC_SECOND;
        } else if ("tz".equalsIgnoreCase(string)
                   || "tzoffset".equalsIgnoreCase(string)) {
            part = Tokens.TIMEZONE;
        } else {
            throw Error.error(ErrorCode.X_42566, string);
        }

        return part;
    }

    private static int getExtractTokenForTSIPart(int part) {

        switch (part) {

            case Tokens.SQL_TSI_FRAC_SECOND :
                return Tokens.NANOSECOND;

            case Tokens.SQL_TSI_MILLI_SECOND :
                return Tokens.MILLISECOND;

            case Tokens.SQL_TSI_SECOND :
                return Tokens.SECOND;

            case Tokens.SQL_TSI_MINUTE :
                return Tokens.MINUTE;

            case Tokens.SQL_TSI_HOUR :
                return Tokens.HOUR;

            case Tokens.SQL_TSI_DAY :
                return Tokens.DAY;

            case Tokens.DAY_OF_WEEK :
                return Tokens.DAY_OF_WEEK;

            case Tokens.DAY_OF_YEAR :
                return Tokens.DAY_OF_YEAR;

            case Tokens.TIMEZONE :
                return Tokens.TIMEZONE;

            case Tokens.SQL_TSI_WEEK :
                return Tokens.WEEK_OF_YEAR;

            case Tokens.SQL_TSI_MONTH :
                return Tokens.MONTH;

            case Tokens.SQL_TSI_QUARTER :
                return Tokens.QUARTER;

            case Tokens.SQL_TSI_YEAR :
                return Tokens.YEAR;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "FunctionCustom");
        }
    }

    static int getSQLTypeForToken(String string) {

        int type = -1;

        if ("YEAR_MONTH".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_YEAR_TO_MONTH;
        } else if ("DAY_HOUR".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_DAY_TO_HOUR;
        } else if ("DAY_MINUTE".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_DAY_TO_MINUTE;
        } else if ("DAY_SECOND".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_DAY_TO_SECOND;
        } else if ("DAY_MICROSECOND".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_DAY_TO_SECOND;
        } else if ("HOUR_MINUTE".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_HOUR_TO_MINUTE;
        } else if ("HOUR_SECOND".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_HOUR_TO_SECOND;
        } else if ("HOUR_MICROSECOND".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_HOUR_TO_SECOND;
        } else if ("MINUTE_SECOND".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_MINUTE_TO_SECOND;
        } else if ("MINUTE_MICROSECOND".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_MINUTE_TO_SECOND;
        } else if ("SECOND_MICROSECOND".equalsIgnoreCase(string)) {
            type = Types.SQL_INTERVAL_SECOND;
        }

        return type;
    }

    private static IntKeyIntValueHashMap getTranslationMap(String source,
            String dest) {

        IntKeyIntValueHashMap map = new IntKeyIntValueHashMap();

        for (int i = 0; i < source.length(); i++) {
            int character = source.charAt(i);

            if (i >= dest.length()) {
                map.put(character, -1);

                continue;
            }

            int value = dest.charAt(i);

            map.put(character, value);
        }

        return map;
    }

    private static String translateWithMap(String source,
                                           IntKeyIntValueHashMap map) {

        StringBuffer sb = new StringBuffer(source.length());

        for (int i = 0; i < source.length(); i++) {
            int character = source.charAt(i);
            int value     = map.get(character, -2);

            if (value == -2) {
                sb.append((char) character);
            } else if (value == -1) {

                //
            } else {
                sb.append((char) value);
            }
        }

        return sb.toString();
    }

    private static int regexpParams(String params) {

        int flags = 0;

        if (params == null) {
            return flags;
        }

        for (int i = 0; i < params.length(); ++i) {
            switch (params.charAt(i)) {

                case 'i' :
                    flags |= Pattern.CASE_INSENSITIVE;
                    break;

                case 'c' :
                    flags &= ~Pattern.CASE_INSENSITIVE;
                    flags |= Pattern.UNICODE_CASE;
                    break;

                case 'n' :
                    flags |= Pattern.DOTALL;
                    break;

                case 'm' :
                    flags |= Pattern.MULTILINE;
                    break;

                default :
                    throw Error.error(ErrorCode.X_22511, params);
            }
        }

        return flags;
    }
}
