/EQEmuJSM/mysql-connector-java-5.1.13/src/com/mysql/jdbc/EscapeProcessor.java
Java | 779 lines | 657 code | 49 blank | 73 comment | 61 complexity | e00bada7a206634bf0bb2fbe3d41e740 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0
- /*
- Copyright 2002-2007 MySQL AB, 2008-2010 Sun Microsystems
- All rights reserved. Use is subject to license terms.
- The MySQL Connector/J is licensed under the terms of the GPL,
- like most MySQL Connectors. There are special exceptions to the
- terms and conditions of the GPL as it is applied to this software,
- see the FLOSS License Exception available on mysql.com.
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License as
- published by the Free Software Foundation; version 2 of the
- License.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Â See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- 02110-1301 USA
- */
- /**
- * EscapeProcessor performs all escape code processing as outlined in the JDBC
- * spec by JavaSoft.
- */
- package com.mysql.jdbc;
- import java.sql.SQLException;
- import java.sql.Time;
- import java.sql.Timestamp;
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.Collections;
- import java.util.GregorianCalendar;
- import java.util.HashMap;
- import java.util.Locale;
- import java.util.Map;
- import java.util.StringTokenizer;
- import java.util.TimeZone;
- class EscapeProcessor {
- private static Map JDBC_CONVERT_TO_MYSQL_TYPE_MAP;
- private static Map JDBC_NO_CONVERT_TO_MYSQL_EXPRESSION_MAP;
- static {
- Map tempMap = new HashMap();
- tempMap.put("BIGINT", "0 + ?");
- tempMap.put("BINARY", "BINARY");
- tempMap.put("BIT", "0 + ?");
- tempMap.put("CHAR", "CHAR");
- tempMap.put("DATE", "DATE");
- tempMap.put("DECIMAL", "0.0 + ?");
- tempMap.put("DOUBLE", "0.0 + ?");
- tempMap.put("FLOAT", "0.0 + ?");
- tempMap.put("INTEGER", "0 + ?");
- tempMap.put("LONGVARBINARY", "BINARY");
- tempMap.put("LONGVARCHAR", "CONCAT(?)");
- tempMap.put("REAL", "0.0 + ?");
- tempMap.put("SMALLINT", "CONCAT(?)");
- tempMap.put("TIME", "TIME");
- tempMap.put("TIMESTAMP", "DATETIME");
- tempMap.put("TINYINT", "CONCAT(?)");
- tempMap.put("VARBINARY", "BINARY");
- tempMap.put("VARCHAR", "CONCAT(?)");
- JDBC_CONVERT_TO_MYSQL_TYPE_MAP = Collections.unmodifiableMap(tempMap);
- tempMap = new HashMap(JDBC_CONVERT_TO_MYSQL_TYPE_MAP);
- tempMap.put("BINARY", "CONCAT(?)");
- tempMap.put("CHAR", "CONCAT(?)");
- tempMap.remove("DATE");
- tempMap.put("LONGVARBINARY", "CONCAT(?)");
- tempMap.remove("TIME");
- tempMap.remove("TIMESTAMP");
- tempMap.put("VARBINARY", "CONCAT(?)");
- JDBC_NO_CONVERT_TO_MYSQL_EXPRESSION_MAP = Collections
- .unmodifiableMap(tempMap);
- }
- /**
- * Escape process one string
- *
- * @param sql
- * the SQL to escape process.
- *
- * @return the SQL after it has been escape processed.
- *
- * @throws java.sql.SQLException
- * DOCUMENT ME!
- * @throws SQLException
- * DOCUMENT ME!
- */
- public static final Object escapeSQL(String sql,
- boolean serverSupportsConvertFn, MySQLConnection conn)
- throws java.sql.SQLException {
- boolean replaceEscapeSequence = false;
- String escapeSequence = null;
- if (sql == null) {
- return null;
- }
- /*
- * Short circuit this code if we don't have a matching pair of "{}". -
- * Suggested by Ryan Gustafason
- */
- int beginBrace = sql.indexOf('{');
- int nextEndBrace = (beginBrace == -1) ? (-1) : sql.indexOf('}',
- beginBrace);
- if (nextEndBrace == -1) {
- return sql;
- }
- StringBuffer newSql = new StringBuffer();
- EscapeTokenizer escapeTokenizer = new EscapeTokenizer(sql);
- byte usesVariables = StatementImpl.USES_VARIABLES_FALSE;
- boolean callingStoredFunction = false;
- while (escapeTokenizer.hasMoreTokens()) {
- String token = escapeTokenizer.nextToken();
- if (token.length() != 0) {
- if (token.charAt(0) == '{') { // It's an escape code
- if (!token.endsWith("}")) {
- throw SQLError
- .createSQLException("Not a valid escape sequence: "
- + token, conn.getExceptionInterceptor());
- }
- if (token.length() > 2) {
- int nestedBrace = token.indexOf('{', 2);
- if (nestedBrace != -1) {
- StringBuffer buf = new StringBuffer(token
- .substring(0, 1));
- Object remainingResults = escapeSQL(token
- .substring(1, token.length() - 1),
- serverSupportsConvertFn, conn);
- String remaining = null;
- if (remainingResults instanceof String) {
- remaining = (String) remainingResults;
- } else {
- remaining = ((EscapeProcessorResult) remainingResults).escapedSql;
- if (usesVariables != StatementImpl.USES_VARIABLES_TRUE) {
- usesVariables = ((EscapeProcessorResult) remainingResults).usesVariables;
- }
- }
- buf.append(remaining);
- buf.append('}');
- token = buf.toString();
- }
- }
- // nested escape code
- // Compare to tokens with _no_ whitespace
- String collapsedToken = removeWhitespace(token);
- /*
- * Process the escape code
- */
- if (StringUtils.startsWithIgnoreCase(collapsedToken,
- "{escape")) {
- try {
- StringTokenizer st = new StringTokenizer(token,
- " '");
- st.nextToken(); // eat the "escape" token
- escapeSequence = st.nextToken();
- if (escapeSequence.length() < 3) {
- newSql.append(token); // it's just part of the
- // query, push possible
- // syntax errors onto
- // server's shoulders
- } else {
- escapeSequence = escapeSequence.substring(1,
- escapeSequence.length() - 1);
- replaceEscapeSequence = true;
- }
- } catch (java.util.NoSuchElementException e) {
- newSql.append(token); // it's just part of the
- // query, push possible
- // syntax errors onto
- // server's shoulders
- }
- } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
- "{fn")) {
- int startPos = token.toLowerCase().indexOf("fn ") + 3;
- int endPos = token.length() - 1; // no }
- String fnToken = token.substring(startPos, endPos);
- // We need to handle 'convert' by ourselves
- if (StringUtils.startsWithIgnoreCaseAndWs(fnToken,
- "convert")) {
- newSql.append(processConvertToken(fnToken,
- serverSupportsConvertFn, conn));
- } else {
- // just pass functions right to the DB
- newSql.append(fnToken);
- }
- } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
- "{d")) {
- int startPos = token.indexOf('\'') + 1;
- int endPos = token.lastIndexOf('\''); // no }
- if ((startPos == -1) || (endPos == -1)) {
- newSql.append(token); // it's just part of the
- // query, push possible
- // syntax errors onto
- // server's shoulders
- } else {
- String argument = token.substring(startPos, endPos);
- try {
- StringTokenizer st = new StringTokenizer(
- argument, " -");
- String year4 = st.nextToken();
- String month2 = st.nextToken();
- String day2 = st.nextToken();
- String dateString = "'" + year4 + "-" + month2
- + "-" + day2 + "'";
- newSql.append(dateString);
- } catch (java.util.NoSuchElementException e) {
- throw SQLError.createSQLException(
- "Syntax error for DATE escape sequence '"
- + argument + "'", "42000", conn.getExceptionInterceptor());
- }
- }
- } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
- "{ts")) {
- processTimestampToken(conn, newSql, token);
- } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
- "{t")) {
- processTimeToken(conn, newSql, token);
- } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
- "{call")
- || StringUtils.startsWithIgnoreCase(collapsedToken,
- "{?=call")) {
- int startPos = StringUtils.indexOfIgnoreCase(token,
- "CALL") + 5;
- int endPos = token.length() - 1;
- if (StringUtils.startsWithIgnoreCase(collapsedToken,
- "{?=call")) {
- callingStoredFunction = true;
- newSql.append("SELECT ");
- newSql.append(token.substring(startPos, endPos));
- } else {
- callingStoredFunction = false;
- newSql.append("CALL ");
- newSql.append(token.substring(startPos, endPos));
- }
- for (int i = endPos - 1; i >= startPos; i--) {
- char c = token.charAt(i);
- if (Character.isWhitespace(c)) {
- continue;
- }
- if (c != ')') {
- newSql.append("()"); // handle no-parenthesis
- // no-arg call not
- // supported
- // by MySQL parser
- }
- break;
- }
- } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
- "{oj")) {
- // MySQL already handles this escape sequence
- // because of ODBC. Cool.
- newSql.append(token);
- }
- } else {
- newSql.append(token); // it's just part of the query
- }
- }
- }
- String escapedSql = newSql.toString();
- //
- // FIXME: Let MySQL do this, however requires
- // lightweight parsing of statement
- //
- if (replaceEscapeSequence) {
- String currentSql = escapedSql;
- while (currentSql.indexOf(escapeSequence) != -1) {
- int escapePos = currentSql.indexOf(escapeSequence);
- String lhs = currentSql.substring(0, escapePos);
- String rhs = currentSql.substring(escapePos + 1, currentSql
- .length());
- currentSql = lhs + "\\" + rhs;
- }
- escapedSql = currentSql;
- }
- EscapeProcessorResult epr = new EscapeProcessorResult();
- epr.escapedSql = escapedSql;
- epr.callingStoredFunction = callingStoredFunction;
- if (usesVariables != StatementImpl.USES_VARIABLES_TRUE) {
- if (escapeTokenizer.sawVariableUse()) {
- epr.usesVariables = StatementImpl.USES_VARIABLES_TRUE;
- } else {
- epr.usesVariables = StatementImpl.USES_VARIABLES_FALSE;
- }
- }
- return epr;
- }
- private static void processTimeToken(MySQLConnection conn,
- StringBuffer newSql, String token) throws SQLException {
- int startPos = token.indexOf('\'') + 1;
- int endPos = token.lastIndexOf('\''); // no }
- if ((startPos == -1) || (endPos == -1)) {
- newSql.append(token); // it's just part of the
- // query, push possible
- // syntax errors onto
- // server's shoulders
- } else {
- String argument = token.substring(startPos, endPos);
- try {
- StringTokenizer st = new StringTokenizer(
- argument, " :");
- String hour = st.nextToken();
- String minute = st.nextToken();
- String second = st.nextToken();
- if (!conn.getUseTimezone()
- || !conn.getUseLegacyDatetimeCode()) {
- String timeString = "'" + hour + ":"
- + minute + ":" + second + "'";
- newSql.append(timeString);
- } else {
- Calendar sessionCalendar = null;
- if (conn != null) {
- sessionCalendar = conn
- .getCalendarInstanceForSessionOrNew();
- } else {
- sessionCalendar = new GregorianCalendar();
- }
- try {
- int hourInt = Integer.parseInt(hour);
- int minuteInt = Integer
- .parseInt(minute);
- int secondInt = Integer
- .parseInt(second);
- synchronized (sessionCalendar) {
- Time toBeAdjusted = TimeUtil
- .fastTimeCreate(
- sessionCalendar,
- hourInt, minuteInt,
- secondInt, conn.getExceptionInterceptor());
- Time inServerTimezone = TimeUtil
- .changeTimezone(
- conn,
- sessionCalendar,
- null,
- toBeAdjusted,
- sessionCalendar
- .getTimeZone(),
- conn
- .getServerTimezoneTZ(),
- false);
- newSql.append("'");
- newSql.append(inServerTimezone
- .toString());
- newSql.append("'");
- }
- } catch (NumberFormatException nfe) {
- throw SQLError
- .createSQLException(
- "Syntax error in TIMESTAMP escape sequence '"
- + token + "'.",
- SQLError.SQL_STATE_ILLEGAL_ARGUMENT, conn.getExceptionInterceptor());
- }
- }
- } catch (java.util.NoSuchElementException e) {
- throw SQLError.createSQLException(
- "Syntax error for escape sequence '"
- + argument + "'", "42000", conn.getExceptionInterceptor());
- }
- }
- }
- private static void processTimestampToken(MySQLConnection conn,
- StringBuffer newSql, String token) throws SQLException {
- int startPos = token.indexOf('\'') + 1;
- int endPos = token.lastIndexOf('\''); // no }
- if ((startPos == -1) || (endPos == -1)) {
- newSql.append(token); // it's just part of the
- // query, push possible
- // syntax errors onto
- // server's shoulders
- } else {
- String argument = token.substring(startPos, endPos);
- try {
- if (!conn.getUseLegacyDatetimeCode()) {
- Timestamp ts = Timestamp.valueOf(argument);
- SimpleDateFormat tsdf = new SimpleDateFormat(
- "''yyyy-MM-dd HH:mm:ss''", Locale.US); //$NON-NLS-1$
- tsdf
- .setTimeZone(conn
- .getServerTimezoneTZ());
- newSql.append(tsdf.format(ts));
- } else {
- StringTokenizer st = new StringTokenizer(
- argument, " .-:");
- try {
- String year4 = st.nextToken();
- String month2 = st.nextToken();
- String day2 = st.nextToken();
- String hour = st.nextToken();
- String minute = st.nextToken();
- String second = st.nextToken();
- /*
- * For now, we get the fractional
- * seconds part, but we don't use it, as
- * MySQL doesn't support it in it's
- * TIMESTAMP data type
- *
- * String fractionalSecond = "";
- *
- * if (st.hasMoreTokens()) {
- * fractionalSecond = st.nextToken(); }
- */
- /*
- * Use the full format because number
- * format will not work for "between"
- * clauses.
- *
- * Ref. Mysql Docs
- *
- * You can specify DATETIME, DATE and
- * TIMESTAMP values using any of a
- * common set of formats:
- *
- * As a string in either 'YYYY-MM-DD
- * HH:MM:SS' or 'YY-MM-DD HH:MM:SS'
- * format.
- *
- * Thanks to Craig Longman for pointing
- * out this bug
- */
- if (!conn.getUseTimezone()
- && !conn
- .getUseJDBCCompliantTimezoneShift()) {
- newSql.append("'").append(year4)
- .append("-").append(month2)
- .append("-").append(day2)
- .append(" ").append(hour)
- .append(":").append(minute)
- .append(":").append(second)
- .append("'");
- } else {
- Calendar sessionCalendar;
- if (conn != null) {
- sessionCalendar = conn
- .getCalendarInstanceForSessionOrNew();
- } else {
- sessionCalendar = new GregorianCalendar();
- sessionCalendar
- .setTimeZone(TimeZone
- .getTimeZone("GMT"));
- }
- try {
- int year4Int = Integer
- .parseInt(year4);
- int month2Int = Integer
- .parseInt(month2);
- int day2Int = Integer
- .parseInt(day2);
- int hourInt = Integer
- .parseInt(hour);
- int minuteInt = Integer
- .parseInt(minute);
- int secondInt = Integer
- .parseInt(second);
- synchronized (sessionCalendar) {
- boolean useGmtMillis = conn
- .getUseGmtMillisForDatetimes();
- Timestamp toBeAdjusted = TimeUtil
- .fastTimestampCreate(
- useGmtMillis,
- useGmtMillis ? Calendar
- .getInstance(TimeZone
- .getTimeZone("GMT"))
- : null,
- sessionCalendar,
- year4Int,
- month2Int,
- day2Int,
- hourInt,
- minuteInt,
- secondInt,
- 0);
- Timestamp inServerTimezone = TimeUtil
- .changeTimezone(
- conn,
- sessionCalendar,
- null,
- toBeAdjusted,
- sessionCalendar
- .getTimeZone(),
- conn
- .getServerTimezoneTZ(),
- false);
- newSql.append("'");
- String timezoneLiteral = inServerTimezone
- .toString();
- int indexOfDot = timezoneLiteral
- .indexOf(".");
- if (indexOfDot != -1) {
- timezoneLiteral = timezoneLiteral
- .substring(0,
- indexOfDot);
- }
- newSql
- .append(timezoneLiteral);
- }
- newSql.append("'");
- } catch (NumberFormatException nfe) {
- throw SQLError
- .createSQLException(
- "Syntax error in TIMESTAMP escape sequence '"
- + token
- + "'.",
- SQLError.SQL_STATE_ILLEGAL_ARGUMENT, conn.getExceptionInterceptor());
- }
- }
- } catch (java.util.NoSuchElementException e) {
- throw SQLError.createSQLException(
- "Syntax error for TIMESTAMP escape sequence '"
- + argument + "'",
- "42000", conn.getExceptionInterceptor());
- }
- }
- } catch (IllegalArgumentException illegalArgumentException) {
- SQLException sqlEx = SQLError
- .createSQLException(
- "Syntax error for TIMESTAMP escape sequence '"
- + argument + "'",
- "42000", conn.getExceptionInterceptor());
- sqlEx.initCause(illegalArgumentException);
- throw sqlEx;
- }
- }
- }
- /**
- * Re-writes {fn convert (expr, type)} as cast(expr AS type)
- *
- * @param functionToken
- * @return
- * @throws SQLException
- */
- private static String processConvertToken(String functionToken,
- boolean serverSupportsConvertFn, MySQLConnection conn) throws SQLException {
- // The JDBC spec requires these types:
- //
- // BIGINT
- // BINARY
- // BIT
- // CHAR
- // DATE
- // DECIMAL
- // DOUBLE
- // FLOAT
- // INTEGER
- // LONGVARBINARY
- // LONGVARCHAR
- // REAL
- // SMALLINT
- // TIME
- // TIMESTAMP
- // TINYINT
- // VARBINARY
- // VARCHAR
- // MySQL supports these types:
- //
- // BINARY
- // CHAR
- // DATE
- // DATETIME
- // SIGNED (integer)
- // UNSIGNED (integer)
- // TIME
- int firstIndexOfParen = functionToken.indexOf("(");
- if (firstIndexOfParen == -1) {
- throw SQLError
- .createSQLException(
- "Syntax error while processing {fn convert (... , ...)} token, missing opening parenthesis in token '"
- + functionToken + "'.",
- SQLError.SQL_STATE_SYNTAX_ERROR, conn.getExceptionInterceptor());
- }
- int tokenLength = functionToken.length();
- int indexOfComma = functionToken.lastIndexOf(",");
- if (indexOfComma == -1) {
- throw SQLError
- .createSQLException(
- "Syntax error while processing {fn convert (... , ...)} token, missing comma in token '"
- + functionToken + "'.",
- SQLError.SQL_STATE_SYNTAX_ERROR, conn.getExceptionInterceptor());
- }
- int indexOfCloseParen = functionToken.indexOf(')', indexOfComma);
- if (indexOfCloseParen == -1) {
- throw SQLError
- .createSQLException(
- "Syntax error while processing {fn convert (... , ...)} token, missing closing parenthesis in token '"
- + functionToken + "'.",
- SQLError.SQL_STATE_SYNTAX_ERROR, conn.getExceptionInterceptor());
- }
- String expression = functionToken.substring(firstIndexOfParen + 1,
- indexOfComma);
- String type = functionToken.substring(indexOfComma + 1,
- indexOfCloseParen);
- String newType = null;
- String trimmedType = type.trim();
- if (StringUtils.startsWithIgnoreCase(trimmedType, "SQL_")) {
- trimmedType = trimmedType.substring(4, trimmedType.length());
- }
- if (serverSupportsConvertFn) {
- newType = (String) JDBC_CONVERT_TO_MYSQL_TYPE_MAP.get(trimmedType
- .toUpperCase(Locale.ENGLISH));
- } else {
- newType = (String) JDBC_NO_CONVERT_TO_MYSQL_EXPRESSION_MAP
- .get(trimmedType.toUpperCase(Locale.ENGLISH));
- // We need a 'special' check here to give a better error message. If
- // we're in this
- // block, the version of MySQL we're connected to doesn't support
- // CAST/CONVERT,
- // so we can't re-write some data type conversions
- // (date,time,timestamp, datetime)
- if (newType == null) {
- throw SQLError
- .createSQLException(
- "Can't find conversion re-write for type '"
- + type
- + "' that is applicable for this server version while processing escape tokens.",
- SQLError.SQL_STATE_GENERAL_ERROR, conn.getExceptionInterceptor());
- }
- }
- if (newType == null) {
- throw SQLError.createSQLException("Unsupported conversion type '"
- + type.trim() + "' found while processing escape token.",
- SQLError.SQL_STATE_GENERAL_ERROR, conn.getExceptionInterceptor());
- }
- int replaceIndex = newType.indexOf("?");
- if (replaceIndex != -1) {
- StringBuffer convertRewrite = new StringBuffer(newType.substring(0,
- replaceIndex));
- convertRewrite.append(expression);
- convertRewrite.append(newType.substring(replaceIndex + 1, newType
- .length()));
- return convertRewrite.toString();
- } else {
- StringBuffer castRewrite = new StringBuffer("CAST(");
- castRewrite.append(expression);
- castRewrite.append(" AS ");
- castRewrite.append(newType);
- castRewrite.append(")");
- return castRewrite.toString();
- }
- }
- /**
- * Removes all whitespace from the given String. We use this to make escape
- * token comparison white-space ignorant.
- *
- * @param toCollapse
- * the string to remove the whitespace from
- *
- * @return a string with _no_ whitespace.
- */
- private static String removeWhitespace(String toCollapse) {
- if (toCollapse == null) {
- return null;
- }
- int length = toCollapse.length();
- StringBuffer collapsed = new StringBuffer(length);
- for (int i = 0; i < length; i++) {
- char c = toCollapse.charAt(i);
- if (!Character.isWhitespace(c)) {
- collapsed.append(c);
- }
- }
- return collapsed.toString();
- }
- }