/* * Copyright (c) 2004, PostgreSQL Global Development Group * See the LICENSE file in the project root for more information. */ package com.amazon.redshift.jdbc; import com.amazon.redshift.util.GT; import com.amazon.redshift.util.RedshiftException; import com.amazon.redshift.util.RedshiftState; import java.lang.reflect.Method; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; /** * This class stores supported escaped function. * * @author Xavier Poinsard * @deprecated see {@link EscapedFunctions2} */ @Deprecated public class EscapedFunctions { // numeric functions names public static final String ABS = "abs"; public static final String ACOS = "acos"; public static final String ASIN = "asin"; public static final String ATAN = "atan"; public static final String ATAN2 = "atan2"; public static final String CEILING = "ceiling"; public static final String COS = "cos"; public static final String COT = "cot"; public static final String DEGREES = "degrees"; public static final String EXP = "exp"; public static final String FLOOR = "floor"; public static final String LOG = "log"; public static final String LOG10 = "log10"; public static final String MOD = "mod"; public static final String PI = "pi"; public static final String POWER = "power"; public static final String RADIANS = "radians"; public static final String RANDOM = "random"; public static final String ROUND = "round"; public static final String SIGN = "sign"; public static final String SIN = "sin"; public static final String SQRT = "sqrt"; public static final String TAN = "tan"; public static final String TRUNCATE = "truncate"; // string function names public static final String ASCII = "ascii"; public static final String CHAR = "char"; public static final String CHAR_LENGTH = "char_length"; public static final String CHARACTER_LENGTH = "character_length"; public static final String CONCAT = "concat"; public static final String INSERT = "insert"; // change arguments order public static final String LCASE = "lcase"; public static final String LEFT = "left"; public static final String LENGTH = "length"; public static final String LOCATE = "locate"; // the 3 args version duplicate args public static final String LTRIM = "ltrim"; public static final String OCTET_LENGTH = "octet_length"; public static final String POSITION = "position"; public static final String REPEAT = "repeat"; public static final String REPLACE = "replace"; public static final String RIGHT = "right"; // duplicate args public static final String RTRIM = "rtrim"; public static final String SPACE = "space"; public static final String SUBSTRING = "substring"; public static final String UCASE = "ucase"; // soundex is implemented on the server side by // the contrib/fuzzystrmatch module. We provide a translation // for this in the driver, but since we don't want to bother with run // time detection of this module's installation we don't report this // method as supported in DatabaseMetaData. // difference is currently unsupported entirely. // date time function names public static final String CURDATE = "curdate"; public static final String CURTIME = "curtime"; public static final String DAYNAME = "dayname"; public static final String DAYOFMONTH = "dayofmonth"; public static final String DAYOFWEEK = "dayofweek"; public static final String DAYOFYEAR = "dayofyear"; public static final String HOUR = "hour"; public static final String MINUTE = "minute"; public static final String MONTH = "month"; public static final String MONTHNAME = "monthname"; public static final String NOW = "now"; public static final String QUARTER = "quarter"; public static final String SECOND = "second"; public static final String WEEK = "week"; public static final String YEAR = "year"; // for timestampadd and timestampdiff the fractional part of second is not supported // by the backend // timestampdiff is very partially supported public static final String TIMESTAMPADD = "timestampadd"; public static final String TIMESTAMPDIFF = "timestampdiff"; // constants for timestampadd and timestampdiff public static final String SQL_TSI_ROOT = "SQL_TSI_"; public static final String SQL_TSI_DAY = "DAY"; public static final String SQL_TSI_FRAC_SECOND = "FRAC_SECOND"; public static final String SQL_TSI_HOUR = "HOUR"; public static final String SQL_TSI_MINUTE = "MINUTE"; public static final String SQL_TSI_MONTH = "MONTH"; public static final String SQL_TSI_QUARTER = "QUARTER"; public static final String SQL_TSI_SECOND = "SECOND"; public static final String SQL_TSI_WEEK = "WEEK"; public static final String SQL_TSI_YEAR = "YEAR"; // system functions public static final String DATABASE = "database"; public static final String IFNULL = "ifnull"; public static final String USER = "user"; /** * storage for functions implementations. */ private static Map functionMap = createFunctionMap(); private static Map createFunctionMap() { Method[] arrayMeths = EscapedFunctions.class.getDeclaredMethods(); Map functionMap = new HashMap(arrayMeths.length * 2); for (Method meth : arrayMeths) { if (meth.getName().startsWith("sql")) { functionMap.put(meth.getName().toLowerCase(Locale.US), meth); } } return functionMap; } /** * get Method object implementing the given function. * * @param functionName name of the searched function * @return a Method object or null if not found */ public static Method getFunction(String functionName) { return functionMap.get("sql" + functionName.toLowerCase(Locale.US)); } // ** numeric functions translations ** /** * ceiling to ceil translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlceiling(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("ceil(", "ceiling", parsedArgs); } /** * log to ln translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqllog(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("ln(", "log", parsedArgs); } /** * log10 to log translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqllog10(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("log(", "log10", parsedArgs); } /** * power to pow translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlpower(List parsedArgs) throws SQLException { return twoArgumentsFunctionCall("pow(", "power", parsedArgs); } /** * truncate to trunc translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqltruncate(List parsedArgs) throws SQLException { return twoArgumentsFunctionCall("trunc(", "truncate", parsedArgs); } // ** string functions translations ** /** * char to chr translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlchar(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("chr(", "char", parsedArgs); } /** * concat translation. * * @param parsedArgs arguments * @return sql call */ public static String sqlconcat(List parsedArgs) { StringBuilder buf = new StringBuilder(); buf.append('('); for (int iArg = 0; iArg < parsedArgs.size(); iArg++) { buf.append(parsedArgs.get(iArg)); if (iArg != (parsedArgs.size() - 1)) { buf.append(" || "); } } return buf.append(')').toString(); } /** * insert to overlay translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlinsert(List parsedArgs) throws SQLException { if (parsedArgs.size() != 4) { throw new RedshiftException(GT.tr("{0} function takes four and only four argument.", "insert"), RedshiftState.SYNTAX_ERROR); } StringBuilder buf = new StringBuilder(); buf.append("overlay("); buf.append(parsedArgs.get(0)).append(" placing ").append(parsedArgs.get(3)); buf.append(" from ").append(parsedArgs.get(1)).append(" for ").append(parsedArgs.get(2)); return buf.append(')').toString(); } /** * lcase to lower translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqllcase(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("lower(", "lcase", parsedArgs); } /** * left to substring translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlleft(List parsedArgs) throws SQLException { if (parsedArgs.size() != 2) { throw new RedshiftException(GT.tr("{0} function takes two and only two arguments.", "left"), RedshiftState.SYNTAX_ERROR); } StringBuilder buf = new StringBuilder(); buf.append("substring("); buf.append(parsedArgs.get(0)).append(" for ").append(parsedArgs.get(1)); return buf.append(')').toString(); } /** * length translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqllength(List parsedArgs) throws SQLException { if (parsedArgs.size() != 1) { throw new RedshiftException(GT.tr("{0} function takes one and only one argument.", "length"), RedshiftState.SYNTAX_ERROR); } StringBuilder buf = new StringBuilder(); buf.append("length(trim(trailing from "); buf.append(parsedArgs.get(0)); return buf.append("))").toString(); } /** * locate translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqllocate(List parsedArgs) throws SQLException { if (parsedArgs.size() == 2) { return "position(" + parsedArgs.get(0) + " in " + parsedArgs.get(1) + ")"; } else if (parsedArgs.size() == 3) { String tmp = "position(" + parsedArgs.get(0) + " in substring(" + parsedArgs.get(1) + " from " + parsedArgs.get(2) + "))"; return "(" + parsedArgs.get(2) + "*sign(" + tmp + ")+" + tmp + ")"; } else { throw new RedshiftException(GT.tr("{0} function takes two or three arguments.", "locate"), RedshiftState.SYNTAX_ERROR); } } /** * ltrim translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlltrim(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("trim(leading from ", "ltrim", parsedArgs); } /** * right to substring translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlright(List parsedArgs) throws SQLException { if (parsedArgs.size() != 2) { throw new RedshiftException(GT.tr("{0} function takes two and only two arguments.", "right"), RedshiftState.SYNTAX_ERROR); } StringBuilder buf = new StringBuilder(); buf.append("substring("); buf.append(parsedArgs.get(0)) .append(" from (length(") .append(parsedArgs.get(0)) .append(")+1-") .append(parsedArgs.get(1)); return buf.append("))").toString(); } /** * rtrim translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlrtrim(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("trim(trailing from ", "rtrim", parsedArgs); } /** * space translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlspace(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("repeat(' ',", "space", parsedArgs); } /** * substring to substr translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlsubstring(List parsedArgs) throws SQLException { if (parsedArgs.size() == 2) { return "substr(" + parsedArgs.get(0) + "," + parsedArgs.get(1) + ")"; } else if (parsedArgs.size() == 3) { return "substr(" + parsedArgs.get(0) + "," + parsedArgs.get(1) + "," + parsedArgs.get(2) + ")"; } else { throw new RedshiftException(GT.tr("{0} function takes two or three arguments.", "substring"), RedshiftState.SYNTAX_ERROR); } } /** * ucase to upper translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlucase(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("upper(", "ucase", parsedArgs); } /** * curdate to current_date translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlcurdate(List parsedArgs) throws SQLException { if (!parsedArgs.isEmpty()) { throw new RedshiftException(GT.tr("{0} function doesn''t take any argument.", "curdate"), RedshiftState.SYNTAX_ERROR); } return "current_date"; } /** * curtime to current_time translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlcurtime(List parsedArgs) throws SQLException { if (!parsedArgs.isEmpty()) { throw new RedshiftException(GT.tr("{0} function doesn''t take any argument.", "curtime"), RedshiftState.SYNTAX_ERROR); } return "current_time"; } /** * dayname translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqldayname(List parsedArgs) throws SQLException { if (parsedArgs.size() != 1) { throw new RedshiftException(GT.tr("{0} function takes one and only one argument.", "dayname"), RedshiftState.SYNTAX_ERROR); } return "to_char(" + parsedArgs.get(0) + ",'Day')"; } /** * dayofmonth translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqldayofmonth(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("extract(day from ", "dayofmonth", parsedArgs); } /** * dayofweek translation adding 1 to Redshift function since we expect values from 1 to 7. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqldayofweek(List parsedArgs) throws SQLException { if (parsedArgs.size() != 1) { throw new RedshiftException(GT.tr("{0} function takes one and only one argument.", "dayofweek"), RedshiftState.SYNTAX_ERROR); } return "extract(dow from " + parsedArgs.get(0) + ")+1"; } /** * dayofyear translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqldayofyear(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("extract(doy from ", "dayofyear", parsedArgs); } /** * hour translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlhour(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("extract(hour from ", "hour", parsedArgs); } /** * minute translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlminute(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("extract(minute from ", "minute", parsedArgs); } /** * month translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlmonth(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("extract(month from ", "month", parsedArgs); } /** * monthname translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlmonthname(List parsedArgs) throws SQLException { if (parsedArgs.size() != 1) { throw new RedshiftException(GT.tr("{0} function takes one and only one argument.", "monthname"), RedshiftState.SYNTAX_ERROR); } return "to_char(" + parsedArgs.get(0) + ",'Month')"; } /** * quarter translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlquarter(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("extract(quarter from ", "quarter", parsedArgs); } /** * second translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlsecond(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("extract(second from ", "second", parsedArgs); } /** * week translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlweek(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("extract(week from ", "week", parsedArgs); } /** * year translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlyear(List parsedArgs) throws SQLException { return singleArgumentFunctionCall("extract(year from ", "year", parsedArgs); } /** * time stamp add. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqltimestampadd(List parsedArgs) throws SQLException { if (parsedArgs.size() != 3) { throw new RedshiftException( GT.tr("{0} function takes three and only three arguments.", "timestampadd"), RedshiftState.SYNTAX_ERROR); } String interval = EscapedFunctions.constantToInterval(parsedArgs.get(0).toString(), parsedArgs.get(1).toString()); StringBuilder buf = new StringBuilder(); buf.append("(").append(interval).append("+"); buf.append(parsedArgs.get(2)).append(")"); return buf.toString(); } private static String constantToInterval(String type, String value) throws SQLException { if (!type.startsWith(SQL_TSI_ROOT)) { throw new RedshiftException(GT.tr("Interval {0} not yet implemented", type), RedshiftState.SYNTAX_ERROR); } String shortType = type.substring(SQL_TSI_ROOT.length()); if (SQL_TSI_DAY.equalsIgnoreCase(shortType)) { return "CAST(" + value + " || ' day' as interval)"; } else if (SQL_TSI_SECOND.equalsIgnoreCase(shortType)) { return "CAST(" + value + " || ' second' as interval)"; } else if (SQL_TSI_HOUR.equalsIgnoreCase(shortType)) { return "CAST(" + value + " || ' hour' as interval)"; } else if (SQL_TSI_MINUTE.equalsIgnoreCase(shortType)) { return "CAST(" + value + " || ' minute' as interval)"; } else if (SQL_TSI_MONTH.equalsIgnoreCase(shortType)) { return "CAST(" + value + " || ' month' as interval)"; } else if (SQL_TSI_QUARTER.equalsIgnoreCase(shortType)) { return "CAST((" + value + "::int * 3) || ' month' as interval)"; } else if (SQL_TSI_WEEK.equalsIgnoreCase(shortType)) { return "CAST(" + value + " || ' week' as interval)"; } else if (SQL_TSI_YEAR.equalsIgnoreCase(shortType)) { return "CAST(" + value + " || ' year' as interval)"; } else if (SQL_TSI_FRAC_SECOND.equalsIgnoreCase(shortType)) { throw new RedshiftException(GT.tr("Interval {0} not yet implemented", "SQL_TSI_FRAC_SECOND"), RedshiftState.SYNTAX_ERROR); } else { throw new RedshiftException(GT.tr("Interval {0} not yet implemented", type), RedshiftState.SYNTAX_ERROR); } } /** * time stamp diff. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqltimestampdiff(List parsedArgs) throws SQLException { if (parsedArgs.size() != 3) { throw new RedshiftException( GT.tr("{0} function takes three and only three arguments.", "timestampdiff"), RedshiftState.SYNTAX_ERROR); } String datePart = EscapedFunctions.constantToDatePart(parsedArgs.get(0).toString()); StringBuilder buf = new StringBuilder(); buf.append("extract( ") .append(datePart) .append(" from (") .append(parsedArgs.get(2)) .append("-") .append(parsedArgs.get(1)) .append("))"); return buf.toString(); } private static String constantToDatePart(String type) throws SQLException { if (!type.startsWith(SQL_TSI_ROOT)) { throw new RedshiftException(GT.tr("Interval {0} not yet implemented", type), RedshiftState.SYNTAX_ERROR); } String shortType = type.substring(SQL_TSI_ROOT.length()); if (SQL_TSI_DAY.equalsIgnoreCase(shortType)) { return "day"; } else if (SQL_TSI_SECOND.equalsIgnoreCase(shortType)) { return "second"; } else if (SQL_TSI_HOUR.equalsIgnoreCase(shortType)) { return "hour"; } else if (SQL_TSI_MINUTE.equalsIgnoreCase(shortType)) { return "minute"; } else if (SQL_TSI_FRAC_SECOND.equalsIgnoreCase(shortType)) { throw new RedshiftException(GT.tr("Interval {0} not yet implemented", "SQL_TSI_FRAC_SECOND"), RedshiftState.SYNTAX_ERROR); } else { throw new RedshiftException(GT.tr("Interval {0} not yet implemented", type), RedshiftState.SYNTAX_ERROR); } // See http://archives.postgresql.org/pgsql-jdbc/2006-03/msg00096.php /* * else if (SQL_TSI_MONTH.equalsIgnoreCase(shortType)) return "month"; else if * (SQL_TSI_QUARTER.equalsIgnoreCase(shortType)) return "quarter"; else if * (SQL_TSI_WEEK.equalsIgnoreCase(shortType)) return "week"; else if * (SQL_TSI_YEAR.equalsIgnoreCase(shortType)) return "year"; */ } /** * database translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqldatabase(List parsedArgs) throws SQLException { if (!parsedArgs.isEmpty()) { throw new RedshiftException(GT.tr("{0} function doesn''t take any argument.", "database"), RedshiftState.SYNTAX_ERROR); } return "current_database()"; } /** * ifnull translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqlifnull(List parsedArgs) throws SQLException { return twoArgumentsFunctionCall("coalesce(", "ifnull", parsedArgs); } /** * user translation. * * @param parsedArgs arguments * @return sql call * @throws SQLException if something wrong happens */ public static String sqluser(List parsedArgs) throws SQLException { if (!parsedArgs.isEmpty()) { throw new RedshiftException(GT.tr("{0} function doesn''t take any argument.", "user"), RedshiftState.SYNTAX_ERROR); } return "user"; } private static String singleArgumentFunctionCall(String call, String functionName, List parsedArgs) throws RedshiftException { if (parsedArgs.size() != 1) { throw new RedshiftException(GT.tr("{0} function takes one and only one argument.", functionName), RedshiftState.SYNTAX_ERROR); } StringBuilder buf = new StringBuilder(); buf.append(call); buf.append(parsedArgs.get(0)); return buf.append(')').toString(); } private static String twoArgumentsFunctionCall(String call, String functionName, List parsedArgs) throws RedshiftException { if (parsedArgs.size() != 2) { throw new RedshiftException(GT.tr("{0} function takes two and only two arguments.", functionName), RedshiftState.SYNTAX_ERROR); } StringBuilder buf = new StringBuilder(); buf.append(call); buf.append(parsedArgs.get(0)).append(',').append(parsedArgs.get(1)); return buf.append(')').toString(); } }