EMMA Coverage Report (generated Fri May 26 15:35:26 CDT 2006)
[all classes][com.mysql.jdbc]

COVERAGE SUMMARY FOR SOURCE FILE [EscapeProcessor.java]

nameclass, %method, %block, %line, %
EscapeProcessor.java100% (1/1)80%  (4/5)74%  (776/1049)85%  (172.2/203)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class EscapeProcessor100% (1/1)80%  (4/5)74%  (776/1049)85%  (172.2/203)
EscapeProcessor (): void 0%   (0/1)0%   (0/3)0%   (0/1)
processConvertToken (String, boolean): String 100% (1/1)59%  (122/207)80%  (28/35)
escapeSQL (String, boolean): Object 100% (1/1)73%  (485/668)83%  (106.2/128)
removeWhitespace (String): String 100% (1/1)94%  (31/33)89%  (8/9)
<static initializer> 100% (1/1)100% (138/138)100% (30/30)

1/*
2 Copyright (C) 2002-2004 MySQL AB
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of version 2 of the GNU General Public License as 
6 published by the Free Software Foundation.
7 
8 There are special exceptions to the terms and conditions of the GPL 
9 as it is applied to this software. View the full text of the 
10 exception in file EXCEPTIONS-CONNECTOR-J in the directory of this 
11 software distribution.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 
22 
23 
24 */
25 
26/**
27 * EscapeProcessor performs all escape code processing as outlined in the JDBC
28 * spec by JavaSoft.
29 */
30package com.mysql.jdbc;
31 
32import java.sql.SQLException;
33 
34import java.util.Collections;
35import java.util.HashMap;
36import java.util.Locale;
37import java.util.Map;
38import java.util.StringTokenizer;
39 
40class EscapeProcessor {
41        private static Map JDBC_CONVERT_TO_MYSQL_TYPE_MAP;
42 
43        private static Map JDBC_NO_CONVERT_TO_MYSQL_EXPRESSION_MAP;
44 
45        static {
46                Map tempMap = new HashMap();
47 
48                tempMap.put("BIGINT", "0 + ?");
49                tempMap.put("BINARY", "BINARY");
50                tempMap.put("BIT", "0 + ?");
51                tempMap.put("CHAR", "CHAR");
52                tempMap.put("DATE", "DATE");
53                tempMap.put("DECIMAL", "0.0 + ?");
54                tempMap.put("DOUBLE", "0.0 + ?");
55                tempMap.put("FLOAT", "0.0 + ?");
56                tempMap.put("INTEGER", "0 + ?");
57                tempMap.put("LONGVARBINARY", "BINARY");
58                tempMap.put("LONGVARCHAR", "CONCAT(?)");
59                tempMap.put("REAL", "0.0 + ?");
60                tempMap.put("SMALLINT", "CONCAT(?)");
61                tempMap.put("TIME", "TIME");
62                tempMap.put("TIMESTAMP", "DATETIME");
63                tempMap.put("TINYINT", "CONCAT(?)");
64                tempMap.put("VARBINARY", "BINARY");
65                tempMap.put("VARCHAR", "CONCAT(?)");
66 
67                JDBC_CONVERT_TO_MYSQL_TYPE_MAP = Collections.unmodifiableMap(tempMap);
68 
69                tempMap = new HashMap(JDBC_CONVERT_TO_MYSQL_TYPE_MAP);
70 
71                tempMap.put("BINARY", "CONCAT(?)");
72                tempMap.put("CHAR", "CONCAT(?)");
73                tempMap.remove("DATE");
74                tempMap.put("LONGVARBINARY", "CONCAT(?)");
75                tempMap.remove("TIME");
76                tempMap.remove("TIMESTAMP");
77                tempMap.put("VARBINARY", "CONCAT(?)");
78 
79                JDBC_NO_CONVERT_TO_MYSQL_EXPRESSION_MAP = Collections
80                                .unmodifiableMap(tempMap);
81 
82        }
83 
84        /**
85         * Escape process one string
86         * 
87         * @param sql
88         *            the SQL to escape process.
89         * 
90         * @return the SQL after it has been escape processed.
91         * 
92         * @throws java.sql.SQLException
93         *             DOCUMENT ME!
94         * @throws SQLException
95         *             DOCUMENT ME!
96         */
97        public static final Object escapeSQL(String sql,
98                        boolean serverSupportsConvertFn) throws java.sql.SQLException {
99                boolean replaceEscapeSequence = false;
100                String escapeSequence = null;
101 
102                if (sql == null) {
103                        return null;
104                }
105 
106                /*
107                 * Short circuit this code if we don't have a matching pair of "{}". -
108                 * Suggested by Ryan Gustafason
109                 */
110                int beginBrace = sql.indexOf('{');
111                int nextEndBrace = (beginBrace == -1) ? (-1) : sql.indexOf('}',
112                                beginBrace);
113 
114                if (nextEndBrace == -1) {
115                        return sql;
116                }
117 
118                StringBuffer newSql = new StringBuffer();
119 
120                EscapeTokenizer escapeTokenizer = new EscapeTokenizer(sql);
121 
122                byte usesVariables = Statement.USES_VARIABLES_FALSE;
123                boolean callingStoredFunction = false;
124 
125                while (escapeTokenizer.hasMoreTokens()) {
126                        String token = escapeTokenizer.nextToken();
127 
128                        if (token.length() != 0) {
129                                if (token.charAt(0) == '{') { // It's an escape code
130 
131                                        if (!token.endsWith("}")) {
132                                                throw new SQLException("Not a valid escape sequence: "
133                                                                + token);
134                                        }
135 
136                                        if (token.length() > 2) {
137                                                int nestedBrace = token.indexOf('{', 2);
138 
139                                                if (nestedBrace != -1) {
140                                                        StringBuffer buf = new StringBuffer(token
141                                                                        .substring(0, 1));
142 
143                                                        Object remainingResults = escapeSQL(token
144                                                                        .substring(1, token.length() - 1),
145                                                                        serverSupportsConvertFn);
146 
147                                                        String remaining = null;
148 
149                                                        if (remainingResults instanceof String) {
150                                                                remaining = (String) remainingResults;
151                                                        } else {
152                                                                remaining = ((EscapeProcessorResult) remainingResults).escapedSql;
153 
154                                                                if (usesVariables != Statement.USES_VARIABLES_TRUE) {
155                                                                        usesVariables = ((EscapeProcessorResult) remainingResults).usesVariables;
156                                                                }
157                                                        }
158 
159                                                        buf.append(remaining);
160 
161                                                        buf.append('}');
162 
163                                                        token = buf.toString();
164                                                }
165                                        }
166 
167                                        // nested escape code
168                                        // Compare to tokens with _no_ whitespace
169                                        String collapsedToken = removeWhitespace(token);
170 
171                                        /*
172                                         * Process the escape code
173                                         */
174                                        if (StringUtils.startsWithIgnoreCase(collapsedToken,
175                                                        "{escape")) {
176                                                try {
177                                                        StringTokenizer st = new StringTokenizer(token,
178                                                                        " '");
179                                                        st.nextToken(); // eat the "escape" token
180                                                        escapeSequence = st.nextToken();
181 
182                                                        if (escapeSequence.length() < 3) {
183                                                                throw new SQLException(
184                                                                                "Syntax error for escape sequence '"
185                                                                                                + token + "'", "42000");
186                                                        }
187 
188                                                        escapeSequence = escapeSequence.substring(1,
189                                                                        escapeSequence.length() - 1);
190                                                        replaceEscapeSequence = true;
191                                                } catch (java.util.NoSuchElementException e) {
192                                                        throw new SQLException(
193                                                                        "Syntax error for escape sequence '"
194                                                                                        + token + "'", "42000");
195                                                }
196                                        } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
197                                                        "{fn")) {
198                                                int startPos = token.toLowerCase().indexOf("fn ") + 3;
199                                                int endPos = token.length() - 1; // no }
200 
201                                                String fnToken = token.substring(startPos, endPos);
202 
203                                                // We need to handle 'convert' by ourselves
204 
205                                                if (StringUtils.startsWithIgnoreCaseAndWs(fnToken,
206                                                                "convert")) {
207                                                        newSql.append(processConvertToken(fnToken,
208                                                                        serverSupportsConvertFn));
209                                                } else {
210                                                        // just pass functions right to the DB
211                                                        newSql.append(fnToken);
212                                                }
213                                        } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
214                                                        "{d")) {
215                                                int startPos = token.indexOf('\'') + 1;
216                                                int endPos = token.lastIndexOf('\''); // no }
217 
218                                                if ((startPos == -1) || (endPos == -1)) {
219                                                        throw new SQLException(
220                                                                        "Syntax error for DATE escape sequence '"
221                                                                                        + token + "'", "42000");
222                                                }
223 
224                                                String argument = token.substring(startPos, endPos);
225 
226                                                try {
227                                                        StringTokenizer st = new StringTokenizer(argument,
228                                                                        " -");
229                                                        String year4 = st.nextToken();
230                                                        String month2 = st.nextToken();
231                                                        String day2 = st.nextToken();
232                                                        String dateString = "'" + year4 + "-" + month2
233                                                                        + "-" + day2 + "'";
234                                                        newSql.append(dateString);
235                                                } catch (java.util.NoSuchElementException e) {
236                                                        throw new SQLException(
237                                                                        "Syntax error for DATE escape sequence '"
238                                                                                        + argument + "'", "42000");
239                                                }
240                                        } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
241                                                        "{ts")) {
242                                                int startPos = token.indexOf('\'') + 1;
243                                                int endPos = token.lastIndexOf('\''); // no }
244 
245                                                if ((startPos == -1) || (endPos == -1)) {
246                                                        throw new SQLException(
247                                                                        "Syntax error for TIMESTAMP escape sequence '"
248                                                                                        + token + "'", "42000");
249                                                }
250 
251                                                String argument = token.substring(startPos, endPos);
252 
253                                                try {
254                                                        StringTokenizer st = new StringTokenizer(argument,
255                                                                        " .-:");
256                                                        String year4 = st.nextToken();
257                                                        String month2 = st.nextToken();
258                                                        String day2 = st.nextToken();
259                                                        String hour = st.nextToken();
260                                                        String minute = st.nextToken();
261                                                        String second = st.nextToken();
262 
263                                                        /*
264                                                         * For now, we get the fractional seconds part, but
265                                                         * we don't use it, as MySQL doesn't support it in
266                                                         * it's TIMESTAMP data type
267                                                         * 
268                                                         * String fractionalSecond = "";
269                                                         * 
270                                                         * if (st.hasMoreTokens()) { fractionalSecond =
271                                                         * st.nextToken(); }
272                                                         */
273                                                        /*
274                                                         * Use the full format because number format will
275                                                         * not work for "between" clauses.
276                                                         * 
277                                                         * Ref. Mysql Docs
278                                                         * 
279                                                         * You can specify DATETIME, DATE and TIMESTAMP
280                                                         * values using any of a common set of formats:
281                                                         * 
282                                                         * As a string in either 'YYYY-MM-DD HH:MM:SS' or
283                                                         * 'YY-MM-DD HH:MM:SS' format.
284                                                         * 
285                                                         * Thanks to Craig Longman for pointing out this bug
286                                                         */
287                                                        newSql.append("'").append(year4).append("-")
288                                                                        .append(month2).append("-").append(day2)
289                                                                        .append(" ").append(hour).append(":")
290                                                                        .append(minute).append(":").append(second)
291                                                                        .append("'");
292                                                } catch (java.util.NoSuchElementException e) {
293                                                        throw new SQLException(
294                                                                        "Syntax error for TIMESTAMP escape sequence '"
295                                                                                        + argument + "'", "42000");
296                                                }
297                                        } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
298                                                        "{t")) {
299                                                int startPos = token.indexOf('\'') + 1;
300                                                int endPos = token.lastIndexOf('\''); // no }
301 
302                                                if ((startPos == -1) || (endPos == -1)) {
303                                                        throw new SQLException(
304                                                                        "Syntax error for TIME escape sequence '"
305                                                                                        + token + "'", "42000");
306                                                }
307 
308                                                String argument = token.substring(startPos, endPos);
309 
310                                                try {
311                                                        StringTokenizer st = new StringTokenizer(argument,
312                                                                        " :");
313                                                        String hour = st.nextToken();
314                                                        String minute = st.nextToken();
315                                                        String second = st.nextToken();
316                                                        String timeString = "'" + hour + ":" + minute + ":"
317                                                                        + second + "'";
318                                                        newSql.append(timeString);
319                                                } catch (java.util.NoSuchElementException e) {
320                                                        throw new SQLException(
321                                                                        "Syntax error for escape sequence '"
322                                                                                        + argument + "'", "42000");
323                                                }
324                                        } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
325                                                        "{call")
326                                                        || StringUtils.startsWithIgnoreCase(collapsedToken,
327                                                                        "{?=call")) {
328 
329                                                int startPos = StringUtils.indexOfIgnoreCase(token,
330                                                                "CALL") + 5;
331                                                int endPos = token.length() - 1;
332 
333                                                if (StringUtils.startsWithIgnoreCase(collapsedToken,
334                                                                "{?=call")) {
335                                                        callingStoredFunction = true;
336                                                        newSql.append("SELECT ");
337                                                        newSql.append(token.substring(startPos, endPos));
338                                                } else {
339                                                        callingStoredFunction = false;
340                                                        newSql.append("CALL ");
341                                                        newSql.append(token.substring(startPos, endPos));
342                                                }
343                                        } else if (StringUtils.startsWithIgnoreCase(collapsedToken,
344                                                        "{oj")) {
345                                                // MySQL already handles this escape sequence
346                                                // because of ODBC. Cool.
347                                                newSql.append(token);
348                                        }
349                                } else {
350                                        newSql.append(token); // it's just part of the query
351                                }
352                        }
353                }
354 
355                String escapedSql = newSql.toString();
356 
357                //
358                // FIXME: Let MySQL do this, however requires
359                // lightweight parsing of statement
360                //
361                if (replaceEscapeSequence) {
362                        String currentSql = escapedSql;
363 
364                        while (currentSql.indexOf(escapeSequence) != -1) {
365                                int escapePos = currentSql.indexOf(escapeSequence);
366                                String lhs = currentSql.substring(0, escapePos);
367                                String rhs = currentSql.substring(escapePos + 1, currentSql
368                                                .length());
369                                currentSql = lhs + "\\" + rhs;
370                        }
371 
372                        escapedSql = currentSql;
373                }
374 
375                EscapeProcessorResult epr = new EscapeProcessorResult();
376                epr.escapedSql = escapedSql;
377                epr.callingStoredFunction = callingStoredFunction;
378 
379                if (usesVariables != Statement.USES_VARIABLES_TRUE) {
380                        if (escapeTokenizer.sawVariableUse()) {
381                                epr.usesVariables = Statement.USES_VARIABLES_TRUE;
382                        } else {
383                                epr.usesVariables = Statement.USES_VARIABLES_FALSE;
384                        }
385                }
386 
387                return epr;
388        }
389 
390        /**
391         * Re-writes {fn convert (expr, type)} as cast(expr AS type)
392         * 
393         * @param functionToken
394         * @return
395         * @throws SQLException
396         */
397        private static String processConvertToken(String functionToken,
398                        boolean serverSupportsConvertFn) throws SQLException {
399                // The JDBC spec requires these types:
400                //
401                // BIGINT
402                // BINARY
403                // BIT
404                // CHAR
405                // DATE
406                // DECIMAL
407                // DOUBLE
408                // FLOAT
409                // INTEGER
410                // LONGVARBINARY
411                // LONGVARCHAR
412                // REAL
413                // SMALLINT
414                // TIME
415                // TIMESTAMP
416                // TINYINT
417                // VARBINARY
418                // VARCHAR
419 
420                // MySQL supports these types:
421                //
422                // BINARY
423                // CHAR
424                // DATE
425                // DATETIME
426                // SIGNED (integer)
427                // UNSIGNED (integer)
428                // TIME
429 
430                int firstIndexOfParen = functionToken.indexOf("(");
431 
432                if (firstIndexOfParen == -1) {
433                        throw new SQLException(
434                                        "Syntax error while processing {fn convert (... , ...)} token, missing opening parenthesis in token '"
435                                                        + functionToken + "'.",
436                                        SQLError.SQL_STATE_SYNTAX_ERROR);
437                }
438 
439                int tokenLength = functionToken.length();
440 
441                int indexOfComma = functionToken.lastIndexOf(",");
442 
443                if (indexOfComma == -1) {
444                        throw new SQLException(
445                                        "Syntax error while processing {fn convert (... , ...)} token, missing comma in token '"
446                                                        + functionToken + "'.",
447                                        SQLError.SQL_STATE_SYNTAX_ERROR);
448                }
449 
450                int indexOfCloseParen = functionToken.indexOf(')', indexOfComma);
451 
452                if (indexOfCloseParen == -1) {
453                        throw new SQLException(
454                                        "Syntax error while processing {fn convert (... , ...)} token, missing closing parenthesis in token '"
455                                                        + functionToken + "'.",
456                                        SQLError.SQL_STATE_SYNTAX_ERROR);
457 
458                }
459 
460                String expression = functionToken.substring(firstIndexOfParen + 1,
461                                indexOfComma);
462                String type = functionToken.substring(indexOfComma + 1,
463                                indexOfCloseParen);
464 
465                String newType = null;
466 
467                String trimmedType = type.trim();
468 
469                if (StringUtils.startsWithIgnoreCase(trimmedType, "SQL_")) {
470                        trimmedType = trimmedType.substring(4, trimmedType.length());
471                }
472 
473                if (serverSupportsConvertFn) {
474                        newType = (String) JDBC_CONVERT_TO_MYSQL_TYPE_MAP.get(trimmedType
475                                        .toUpperCase(Locale.ENGLISH));
476                } else {
477                        newType = (String) JDBC_NO_CONVERT_TO_MYSQL_EXPRESSION_MAP
478                                        .get(trimmedType.toUpperCase(Locale.ENGLISH));
479 
480                        // We need a 'special' check here to give a better error message. If
481                        // we're in this
482                        // block, the version of MySQL we're connected to doesn't support
483                        // CAST/CONVERT,
484                        // so we can't re-write some data type conversions
485                        // (date,time,timestamp, datetime)
486 
487                        if (newType == null) {
488                                throw new SQLException(
489                                                "Can't find conversion re-write for type '"
490                                                                + type
491                                                                + "' that is applicable for this server version while processing escape tokens.",
492                                                SQLError.SQL_STATE_GENERAL_ERROR);
493                        }
494                }
495 
496                if (newType == null) {
497                        throw new SQLException("Unsupported conversion type '"
498                                        + type.trim() + "' found while processing escape token.",
499                                        SQLError.SQL_STATE_GENERAL_ERROR);
500                }
501 
502                int replaceIndex = newType.indexOf("?");
503 
504                if (replaceIndex != -1) {
505                        StringBuffer convertRewrite = new StringBuffer(newType.substring(0,
506                                        replaceIndex));
507                        convertRewrite.append(expression);
508                        convertRewrite.append(newType.substring(replaceIndex + 1, newType
509                                        .length()));
510 
511                        return convertRewrite.toString();
512                } else {
513 
514                        StringBuffer castRewrite = new StringBuffer("CAST(");
515                        castRewrite.append(expression);
516                        castRewrite.append(" AS ");
517                        castRewrite.append(newType);
518                        castRewrite.append(")");
519 
520                        return castRewrite.toString();
521                }
522        }
523 
524        /**
525         * Removes all whitespace from the given String. We use this to make escape
526         * token comparison white-space ignorant.
527         * 
528         * @param toCollapse
529         *            the string to remove the whitespace from
530         * 
531         * @return a string with _no_ whitespace.
532         */
533        private static String removeWhitespace(String toCollapse) {
534                if (toCollapse == null) {
535                        return null;
536                }
537 
538                int length = toCollapse.length();
539 
540                StringBuffer collapsed = new StringBuffer(length);
541 
542                for (int i = 0; i < length; i++) {
543                        char c = toCollapse.charAt(i);
544 
545                        if (!Character.isWhitespace(c)) {
546                                collapsed.append(c);
547                        }
548                }
549 
550                return collapsed.toString();
551        }
552}

[all classes][com.mysql.jdbc]
EMMA 2.0.4217 (C) Vladimir Roubtsov