EscapeProcessor.java
上传用户:sxlinghang
上传日期:2022-07-20
资源大小:1405k
文件大小:18k
源码类别:

数据库编程

开发平台:

Java

  1. /*
  2.    Copyright (C) 2002 MySQL AB
  3.       This program is free software; you can redistribute it and/or modify
  4.       it under the terms of the GNU General Public License as published by
  5.       the Free Software Foundation; either version 2 of the License, or
  6.       (at your option) any later version.
  7.       This program is distributed in the hope that it will be useful,
  8.       but WITHOUT ANY WARRANTY; without even the implied warranty of
  9.       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  10.       GNU General Public License for more details.
  11.       You should have received a copy of the GNU General Public License
  12.       along with this program; if not, write to the Free Software
  13.       Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  14.  */
  15. /**
  16.  * EscapeProcessor performs all escape code processing as outlined
  17.  * in the JDBC spec by JavaSoft.
  18.  *
  19.  * @author Mark Matthews
  20.  * @version $Id: EscapeProcessor.java,v 1.9.2.8 2003/12/24 05:16:25 mmatthew Exp $
  21.  */
  22. package com.mysql.jdbc;
  23. import java.sql.SQLException;
  24. import java.util.Collections;
  25. import java.util.HashMap;
  26. import java.util.Map;
  27. import java.util.StringTokenizer;
  28. class EscapeProcessor {
  29.     /**
  30.      * Escape process one string
  31.      *
  32.      * @param SQL the SQL to escape process.
  33.      * @param serverSupportsConvertFn does the server support CONVERT() or CAST()?
  34.      * 
  35.      * @return the SQL after it has been escape processed.
  36.      */
  37.     public static final String escapeSQL(String sql, boolean serverSupportsConvertFn)
  38.         throws java.sql.SQLException {
  39.         boolean replaceEscapeSequence = false;
  40.         String escapeSequence = null;
  41.         StringBuffer newSql = new StringBuffer();
  42.         if (sql == null) {
  43.             return null;
  44.         }
  45.         /*
  46.          * Short circuit this code if we don't have a matching pair of
  47.          * "{}". - Suggested by Ryan Gustafason
  48.          */
  49.         int beginBrace = sql.indexOf('{');
  50.         int nextEndBrace = (beginBrace == -1) ? (-1)
  51.                                               : sql.indexOf('}', beginBrace);
  52.         if (nextEndBrace == -1) {
  53.             return sql;
  54.         }
  55.         EscapeTokenizer escapeTokenizer = new EscapeTokenizer(sql);
  56.         while (escapeTokenizer.hasMoreTokens()) {
  57.             String token = escapeTokenizer.nextToken();
  58.             if (token.startsWith("{")) { // It's an escape code
  59.                 if (!token.endsWith("}")) {
  60.                     throw new java.sql.SQLException(
  61.                         "Not a valid escape sequence: " + token);
  62.                 }
  63.                 if (token.length() > 2) {
  64.                     int nestedBrace = token.indexOf('{', 2);
  65.                     if (nestedBrace != -1) {
  66.                         StringBuffer buf = new StringBuffer(token.substring(0, 1));
  67.                         String remaining = escapeSQL(token.substring(1,
  68.                                     token.length() - 1), serverSupportsConvertFn);
  69.                         buf.append(remaining);
  70.                         buf.append('}');
  71.                         token = buf.toString();
  72.                     }
  73.                 }
  74.                  // nested escape code
  75. // Compare to tokens with _no_ whitespace
  76. String collapsedToken = removeWhitespace(token);
  77.                 /*
  78.                  * Process the escape code
  79.                  */
  80.                 if (StringUtils.startsWithIgnoreCase(collapsedToken, "{escape")) {
  81.                     try {
  82.                         StringTokenizer st = new StringTokenizer(token, " '");
  83.                         st.nextToken(); // eat the "escape" token
  84.                         escapeSequence = st.nextToken();
  85.                         if (escapeSequence.length() < 3) {
  86.                             throw new java.sql.SQLException(
  87.                                 "Syntax error for escape sequence '" + token
  88.                                 + "'", SQLError.SQL_STATE_SYNTAX_ERROR);
  89.                         }
  90.                         escapeSequence = escapeSequence.substring(1,
  91.                                 escapeSequence.length() - 1);
  92.                         replaceEscapeSequence = true;
  93.                     } catch (java.util.NoSuchElementException e) {
  94.                         throw new java.sql.SQLException(
  95.                             "Syntax error for escape sequence '" + token + "'",
  96.                             SQLError.SQL_STATE_SYNTAX_ERROR);
  97.                     }
  98.                 } else if (StringUtils.startsWithIgnoreCase(collapsedToken, "{fn")) {
  99.                 
  100.                 
  101.                     
  102.                     int startPos = token.toLowerCase().indexOf("fn ") + 3;
  103.                     int endPos = token.length() - 1; // no }
  104.                     
  105.                     String fnToken = token.substring(startPos, endPos);
  106.                     
  107.                     // We need to handle 'convert' by ourselves
  108.                     
  109.                     if (StringUtils.startsWithIgnoreCaseAndWs(fnToken, "convert")) {
  110.                      newSql.append(processConvertToken(fnToken, serverSupportsConvertFn));
  111.                     } else {
  112.                      // just pass functions right to the DB
  113.                      newSql.append(fnToken);
  114.                     }
  115.                 } else if (StringUtils.startsWithIgnoreCase(collapsedToken, "{d")) {
  116.                     int startPos = token.indexOf(''') + 1;
  117.                     int endPos = token.lastIndexOf('''); // no }
  118.                     if ((startPos == -1) || (endPos == -1)) {
  119.                         throw new java.sql.SQLException(
  120.                             "Syntax error for DATE escape sequence '" + token
  121.                             + "'", SQLError.SQL_STATE_SYNTAX_ERROR);
  122.                     }
  123.                     String argument = token.substring(startPos, endPos);
  124.                     try {
  125.                         StringTokenizer st = new StringTokenizer(argument, " -");
  126.                         String year4 = st.nextToken();
  127.                         String month2 = st.nextToken();
  128.                         String day2 = st.nextToken();
  129.                         String dateString = "'" + year4 + "-" + month2 + "-"
  130.                             + day2 + "'";
  131.                         newSql.append(dateString);
  132.                     } catch (java.util.NoSuchElementException e) {
  133.                         throw new java.sql.SQLException(
  134.                             "Syntax error for DATE escape sequence '"
  135.                             + argument + "'", SQLError.SQL_STATE_SYNTAX_ERROR);
  136.                     }
  137.                 } else if (StringUtils.startsWithIgnoreCase(collapsedToken, "{ts")) {
  138.                     int startPos = token.indexOf(''') + 1;
  139.                     int endPos = token.lastIndexOf('''); // no }
  140.                     if ((startPos == -1) || (endPos == -1)) {
  141.                         throw new java.sql.SQLException(
  142.                             "Syntax error for TIMESTAMP escape sequence '"
  143.                             + token + "'", SQLError.SQL_STATE_SYNTAX_ERROR);
  144.                     }
  145.                     String argument = token.substring(startPos, endPos);
  146.                     try {
  147.                         StringTokenizer st = new StringTokenizer(argument,
  148.                                 " .-:");
  149.                         String year4 = st.nextToken();
  150.                         String month2 = st.nextToken();
  151.                         String day2 = st.nextToken();
  152.                         String hour = st.nextToken();
  153.                         String minute = st.nextToken();
  154.                         String second = st.nextToken();
  155.                         /*
  156.                          * For now, we get the fractional seconds
  157.                          * part, but we don't use it, as MySQL doesn't
  158.                          * support it in it's TIMESTAMP data type
  159.                          *
  160.                         String fractionalSecond = "";
  161.                         if (st.hasMoreTokens()) {
  162.                             fractionalSecond = st.nextToken();
  163.                         }
  164.                         */
  165.                         /*
  166.                          * Use the full format because number format
  167.                          * will not work for "between" clauses.
  168.                          *
  169.                          * Ref. Mysql Docs
  170.                          *
  171.                          * You can specify DATETIME, DATE and TIMESTAMP values
  172.                          * using any of a common set of formats:
  173.                          *
  174.                          * As a string in either 'YYYY-MM-DD HH:MM:SS' or
  175.                          * 'YY-MM-DD HH:MM:SS' format.
  176.                          *
  177.                          * Thanks to Craig Longman for pointing out this bug
  178.                          */
  179.                         newSql.append("'").append(year4).append("-")
  180.                               .append(month2).append("-").append(day2)
  181.                               .append(" ").append(hour).append(":")
  182.                               .append(minute).append(":").append(second).append("'");
  183.                     } catch (java.util.NoSuchElementException e) {
  184.                         throw new java.sql.SQLException(
  185.                             "Syntax error for TIMESTAMP escape sequence '"
  186.                             + argument + "'", SQLError.SQL_STATE_SYNTAX_ERROR);
  187.                     }
  188.                 } else if (StringUtils.startsWithIgnoreCase(collapsedToken, "{t")) {
  189.                     int startPos = token.indexOf(''') + 1;
  190.                     int endPos = token.lastIndexOf('''); // no }
  191.                     if ((startPos == -1) || (endPos == -1)) {
  192.                         throw new java.sql.SQLException(
  193.                             "Syntax error for TIME escape sequence '" + token
  194.                             + "'", SQLError.SQL_STATE_SYNTAX_ERROR);
  195.                     }
  196.                     String argument = token.substring(startPos, endPos);
  197.                     try {
  198.                         StringTokenizer st = new StringTokenizer(argument, " :");
  199.                         String hour = st.nextToken();
  200.                         String minute = st.nextToken();
  201.                         String second = st.nextToken();
  202.                         String timeString = "'" + hour + ":" + minute + ":"
  203.                             + second + "'";
  204.                         newSql.append(timeString);
  205.                     } catch (java.util.NoSuchElementException e) {
  206.                         throw new java.sql.SQLException(
  207.                             "Syntax error for escape sequence '" + argument
  208.                             + "'", SQLError.SQL_STATE_SYNTAX_ERROR);
  209.                     }
  210.                 } else if (StringUtils.startsWithIgnoreCase(collapsedToken, "{call")
  211.                         || StringUtils.startsWithIgnoreCase(collapsedToken, "{?=call")) {
  212.                     throw new java.sql.SQLException(
  213.                         "Stored procedures not supported: " + token, "S1C00");
  214.                 } else if (StringUtils.startsWithIgnoreCase(collapsedToken, "{oj")) {
  215.                     // MySQL already handles this escape sequence
  216.                     // because of ODBC. Cool.
  217.                     newSql.append(token);
  218.                 }
  219.             } else {
  220.                 newSql.append(token); // it's just part of the query
  221.             }
  222.         }
  223.         String escapedSql = newSql.toString();
  224.         //
  225.         // FIXME: Let MySQL do this, however requires
  226.         //        lightweight parsing of statement
  227.         //
  228.         if (replaceEscapeSequence) {
  229.             String currentSql = escapedSql;
  230.             while (currentSql.indexOf(escapeSequence) != -1) {
  231.                 int escapePos = currentSql.indexOf(escapeSequence);
  232.                 String lhs = currentSql.substring(0, escapePos);
  233.                 String rhs = currentSql.substring(escapePos + 1,
  234.                         currentSql.length());
  235.                 currentSql = lhs + "\" + rhs;
  236.             }
  237.             escapedSql = currentSql;
  238.         }
  239.         return escapedSql;
  240.     }
  241.     
  242.     /**
  243.      * Removes all whitespace from the given String. We use
  244.      * this to make escape token comparison white-space ignorant.
  245.      *  
  246.      * @param toCollapse the string to remove the whitespace from
  247.      * @return a string with _no_ whitespace.
  248.      */
  249.     private static String removeWhitespace(String toCollapse) {
  250.      if (toCollapse == null) {
  251.      return null;
  252.      }
  253.     
  254.      int length = toCollapse.length();
  255.     
  256.      StringBuffer collapsed = new StringBuffer(length);
  257.     
  258.      for (int i = 0; i < length; i++) {
  259.      char c = toCollapse.charAt(i);
  260.     
  261.      if (!Character.isWhitespace(c)) {
  262.      collapsed.append(c);
  263.      }
  264.      }
  265.     
  266.      return collapsed.toString();
  267.     }
  268.     
  269.     private static Map JDBC_CONVERT_TO_MYSQL_TYPE_MAP;
  270.     private static Map JDBC_NO_CONVERT_TO_MYSQL_EXPRESSION_MAP;
  271.     
  272.     static {
  273.      Map tempMap = new HashMap();
  274.     
  275.      tempMap.put("BIGINT", "0 + ?");
  276. tempMap.put("BINARY", "BINARY");
  277. tempMap.put("BIT", "0 + ?");
  278. tempMap.put("CHAR", "CHAR");
  279. tempMap.put("DATE", "DATE");
  280. tempMap.put("DECIMAL", "0.0 + ?");
  281. tempMap.put("DOUBLE", "0.0 + ?");
  282. tempMap.put("FLOAT", "0.0 + ?");
  283. tempMap.put("INTEGER", "0 + ?");
  284. tempMap.put("LONGVARBINARY", "BINARY");
  285. tempMap.put("LONGVARCHAR", "CONCAT(?)");
  286. tempMap.put("REAL", "0.0 + ?");
  287. tempMap.put("SMALLINT", "CONCAT(?)");
  288. tempMap.put("TIME", "TIME");
  289. tempMap.put("TIMESTAMP", "DATETIME");
  290. tempMap.put("TINYINT", "CONCAT(?)");
  291. tempMap.put("VARBINARY", "BINARY");
  292. tempMap.put("VARCHAR", "CONCAT(?)");
  293. JDBC_CONVERT_TO_MYSQL_TYPE_MAP = Collections.unmodifiableMap(tempMap);
  294. tempMap = new HashMap(JDBC_CONVERT_TO_MYSQL_TYPE_MAP);
  295. tempMap.put("BINARY", "CONCAT(?)");
  296. tempMap.put("CHAR", "CONCAT(?)");
  297. tempMap.remove("DATE");
  298. tempMap.put("LONGVARBINARY", "CONCAT(?)");
  299. tempMap.remove("TIME");
  300. tempMap.remove("TIMESTAMP");
  301. tempMap.put("VARBINARY", "CONCAT(?)");
  302. JDBC_NO_CONVERT_TO_MYSQL_EXPRESSION_MAP = Collections.unmodifiableMap(tempMap);
  303.     }
  304.     
  305.     /**
  306.      * Re-writes {fn convert (expr, type)} as cast(expr AS type)
  307.      * @param functionToken
  308.      * @return
  309.      * @throws SQLException
  310.      */
  311.     private static String processConvertToken(String functionToken, boolean serverSupportsConvertFn) throws SQLException {
  312.      // The JDBC spec requires these types:
  313.      //
  314.      // BIGINT
  315.      // BINARY
  316. // BIT
  317.      // CHAR
  318.      // DATE
  319.      // DECIMAL
  320.      // DOUBLE
  321.      // FLOAT
  322.      // INTEGER
  323.      // LONGVARBINARY
  324. // LONGVARCHAR
  325.      // REAL
  326.      // SMALLINT
  327.      // TIME
  328.      // TIMESTAMP
  329.      // TINYINT
  330.      // VARBINARY
  331. // VARCHAR
  332.     
  333.      // MySQL supports these types:
  334.      //
  335.      // BINARY
  336.      // CHAR
  337.      // DATE
  338.      // DATETIME
  339.      // SIGNED (integer)
  340.      // UNSIGNED (integer)
  341.      // TIME
  342.     
  343.      int firstIndexOfParen = functionToken.indexOf("(");
  344.     
  345.      if (firstIndexOfParen == -1) {
  346. throw new SQLException("Syntax error while processing {fn convert (... , ...)} token, missing opening parenthesis in token '" + functionToken + "'.", SQLError.SQL_STATE_SYNTAX_ERROR);
  347.      }
  348.     
  349.      int tokenLength = functionToken.length();
  350.     
  351.      int indexOfComma = functionToken.lastIndexOf(",");
  352.     
  353.      if (indexOfComma == -1) {
  354. throw new SQLException("Syntax error while processing {fn convert (... , ...)} token, missing comma in token '" + functionToken + "'.", SQLError.SQL_STATE_SYNTAX_ERROR);
  355.      }
  356.  
  357.      int indexOfCloseParen = functionToken.indexOf(')', indexOfComma);
  358.     
  359.      if (indexOfCloseParen == -1) {
  360.      throw new SQLException("Syntax error while processing {fn convert (... , ...)} token, missing closing parenthesis in token '" + functionToken + "'.", SQLError.SQL_STATE_SYNTAX_ERROR);
  361.     
  362.      }
  363.     
  364.      String expression = functionToken.substring(firstIndexOfParen + 1, indexOfComma);
  365.      String type = functionToken.substring(indexOfComma + 1, indexOfCloseParen);
  366.     
  367.      String newType = null;
  368.     
  369.      if (serverSupportsConvertFn) {
  370.      newType = (String)JDBC_CONVERT_TO_MYSQL_TYPE_MAP.get(type.trim().toUpperCase());
  371.      } else {
  372.      newType = (String)JDBC_NO_CONVERT_TO_MYSQL_EXPRESSION_MAP.get(type.trim().toUpperCase());
  373.     
  374.      // We need a 'special' check here to give a better error message. If we're in this
  375.      // block, the version of MySQL we're connected to doesn't support CAST/CONVERT,
  376.      // so we can't re-write some data type conversions (date,time,timestamp, datetime)
  377.     
  378.      if (newType == null) {
  379.      throw new SQLException("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);
  380.      }
  381.      }
  382.     
  383.      if (newType == null) {
  384.      throw new SQLException("Unsupported conversion type '" + type.trim() + "' found while processing escape token.", SQLError.SQL_STATE_GENERAL_ERROR);
  385.      }
  386.     
  387.      int replaceIndex = newType.indexOf("?");
  388.     
  389.      if (replaceIndex != -1) {
  390.      StringBuffer convertRewrite = new StringBuffer(newType.substring(0, replaceIndex));
  391.      convertRewrite.append(expression);
  392.      convertRewrite.append(newType.substring(replaceIndex + 1, newType.length()));
  393.     
  394.      return convertRewrite.toString();
  395.      } else {
  396.     
  397.      StringBuffer castRewrite = new StringBuffer("CAST(");
  398.      castRewrite.append(expression);
  399.      castRewrite.append(" AS ");
  400.      castRewrite.append(newType);
  401.      castRewrite.append(")");
  402.     
  403.      return castRewrite.toString();
  404.      }
  405.     }
  406. }