Source Code Cross Referenced for SQLParser.java in  » Database-JDBC-Connection-Pool » jTDS » net » sourceforge » jtds » jdbc » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Database JDBC Connection Pool » jTDS » net.sourceforge.jtds.jdbc 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        // jTDS JDBC Driver for Microsoft SQL Server and Sybase
0002:        // Copyright (C) 2004 The jTDS Project
0003:        //
0004:        // This library is free software; you can redistribute it and/or
0005:        // modify it under the terms of the GNU Lesser General Public
0006:        // License as published by the Free Software Foundation; either
0007:        // version 2.1 of the License, or (at your option) any later version.
0008:        //
0009:        // This library is distributed in the hope that it will be useful,
0010:        // but WITHOUT ANY WARRANTY; without even the implied warranty of
0011:        // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012:        // Lesser General Public License for more details.
0013:        //
0014:        // You should have received a copy of the GNU Lesser General Public
0015:        // License along with this library; if not, write to the Free Software
0016:        // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
0017:        //
0018:        package net.sourceforge.jtds.jdbc;
0019:
0020:        import java.sql.SQLException;
0021:        import java.util.ArrayList;
0022:        import java.util.HashMap;
0023:
0024:        import net.sourceforge.jtds.jdbc.cache.SimpleLRUCache;
0025:        import net.sourceforge.jtds.jdbc.cache.SQLCacheKey;
0026:
0027:        /**
0028:         * Process JDBC escape strings and parameter markers in the SQL string.
0029:         * <p>
0030:         * This code recognizes the following escapes:
0031:         * <ol>
0032:         * <li>Date      {d 'yyyy-mm-dd'}
0033:         * <li>Time      {t 'hh:mm:ss'}
0034:         * <li>Timestamp {ts 'yyyy-mm-dd hh:mm:ss.nnn'}
0035:         * <li>ESCAPE    {escape 'x'}
0036:         * <li>Function  {fn xxxx([arg,arg...])}
0037:         * NB The concat(arg, arg) operator is converted to (arg + arg)
0038:         * <li>OuterJoin {oj .....}
0039:         * <li>Call      {?=call proc [arg, arg...]}
0040:         * or        {call proc [arg, arg...]}
0041:         * </ol>
0042:         * Notes:
0043:         * <ol>
0044:         * <li>This code is designed to be as efficient as possible and as
0045:         * result the validation done here is limited.
0046:         * <li>SQL comments are parsed correctly thanks to code supplied by
0047:         * Joel Fouse.
0048:         * </ol>
0049:         *
0050:         * @author Mike Hutchinson
0051:         * @version $Id: SQLParser.java,v 1.29 2007/07/08 17:28:23 bheineman Exp $
0052:         */
0053:        class SQLParser {
0054:            /**
0055:             * Serialized version of a parsed SQL query (the value stored in the cache
0056:             * for a parsed SQL).
0057:             * <p/>
0058:             * Holds the parsed SQL query and the names, positions and return value and
0059:             * unicode flags for the parameters.
0060:             */
0061:            private static class CachedSQLQuery {
0062:                final String[] parsedSql;
0063:                final String[] paramNames;
0064:                final int[] paramMarkerPos;
0065:                final boolean[] paramIsRetVal;
0066:                final boolean[] paramIsUnicode;
0067:
0068:                CachedSQLQuery(String[] parsedSql, ArrayList params) {
0069:                    this .parsedSql = parsedSql;
0070:
0071:                    if (params != null) {
0072:                        final int size = params.size();
0073:                        paramNames = new String[size];
0074:                        paramMarkerPos = new int[size];
0075:                        paramIsRetVal = new boolean[size];
0076:                        paramIsUnicode = new boolean[size];
0077:
0078:                        for (int i = 0; i < size; i++) {
0079:                            ParamInfo paramInfo = (ParamInfo) params.get(i);
0080:                            paramNames[i] = paramInfo.name;
0081:                            paramMarkerPos[i] = paramInfo.markerPos;
0082:                            paramIsRetVal[i] = paramInfo.isRetVal;
0083:                            paramIsUnicode[i] = paramInfo.isUnicode;
0084:                        }
0085:                    } else {
0086:                        paramNames = null;
0087:                        paramMarkerPos = null;
0088:                        paramIsRetVal = null;
0089:                        paramIsUnicode = null;
0090:                    }
0091:                }
0092:            }
0093:
0094:            /** LRU cache of previously parsed SQL */
0095:            private static SimpleLRUCache cache;
0096:
0097:            /** Original SQL string */
0098:            private final String sql;
0099:            /** Input buffer with SQL statement. */
0100:            private final char[] in;
0101:            /** Current position in input buffer. */
0102:            private int s;
0103:            /** Length of input buffer. */
0104:            private final int len;
0105:            /** Output buffer to contain parsed SQL. */
0106:            private final char[] out;
0107:            /** Current position in output buffer. */
0108:            private int d;
0109:            /**
0110:             * Parameter list to be populated or <code>null</code> if no parameters
0111:             * are expected.
0112:             */
0113:            private final ArrayList params;
0114:            /** Current expected terminator character. */
0115:            private char terminator;
0116:            /** Procedure name in call escape. */
0117:            private String procName;
0118:            /** First SQL keyword or identifier in statement. */
0119:            private String keyWord;
0120:            /** First table name in from clause */
0121:            private String tableName;
0122:            /** Connection object for server specific parsing. */
0123:            private final ConnectionJDBC2 connection;
0124:
0125:            /**
0126:             * Parse the SQL statement processing JDBC escapes and parameter markers.
0127:             *
0128:             * @param extractTable
0129:             *            true to return the first table name in the FROM clause of a select
0130:             * @return The processed SQL statement, any procedure name, the first SQL
0131:             *         keyword and (optionally) the first table name as
0132:             *         elements 0 1, 2 and 3 of the returned <code>String[]</code>.
0133:             * @throws SQLException if a parse error occurs
0134:             */
0135:            static String[] parse(String sql, ArrayList paramList,
0136:                    ConnectionJDBC2 connection, boolean extractTable)
0137:                    throws SQLException {
0138:                // Don't cache extract table parse requests, just process it
0139:                if (extractTable) {
0140:                    SQLParser parser = new SQLParser(sql, paramList, connection);
0141:                    return parser.parse(extractTable);
0142:                }
0143:
0144:                SimpleLRUCache cache = getCache(connection);
0145:
0146:                SQLCacheKey cacheKey = new SQLCacheKey(sql, connection);
0147:
0148:                // By not synchronizing on the cache, we're admitting that the possibility of multiple
0149:                // parses of the same statement can occur.  However, it is 1) unlikely under normal
0150:                // usage, and 2) harmless to the cache.  By avoiding a synchronization block around
0151:                // the get()-parse()-put(), we reduce the contention greatly in the nominal case.
0152:                CachedSQLQuery cachedQuery = (CachedSQLQuery) cache
0153:                        .get(cacheKey);
0154:                if (cachedQuery == null) {
0155:                    // Parse and cache SQL
0156:                    SQLParser parser = new SQLParser(sql, paramList, connection);
0157:                    cachedQuery = new CachedSQLQuery(
0158:                            parser.parse(extractTable), paramList);
0159:                    cache.put(cacheKey, cachedQuery);
0160:                } else {
0161:                    // Create full ParamInfo objects out of cached object
0162:                    final int length = (cachedQuery.paramNames == null) ? 0
0163:                            : cachedQuery.paramNames.length;
0164:                    for (int i = 0; i < length; i++) {
0165:                        ParamInfo paramInfo = new ParamInfo(
0166:                                cachedQuery.paramNames[i],
0167:                                cachedQuery.paramMarkerPos[i],
0168:                                cachedQuery.paramIsRetVal[i],
0169:                                cachedQuery.paramIsUnicode[i]);
0170:                        paramList.add(paramInfo);
0171:                    }
0172:                }
0173:                return cachedQuery.parsedSql;
0174:            }
0175:
0176:            // --------------------------- Private Methods --------------------------------
0177:
0178:            /**
0179:             * Retrieves the statement cache, creating it if required.
0180:             *
0181:             * @return the cache as a <code>SimpleLRUCache</code>
0182:             */
0183:            private synchronized static SimpleLRUCache getCache(
0184:                    ConnectionJDBC2 connection) {
0185:                if (cache == null) {
0186:                    int maxStatements = connection.getMaxStatements();
0187:                    maxStatements = Math.max(0, maxStatements);
0188:                    maxStatements = Math.min(1000, maxStatements);
0189:                    cache = new SimpleLRUCache(maxStatements);
0190:                }
0191:                return cache;
0192:            }
0193:
0194:            /** Lookup table to test if character is part of an identifier. */
0195:            private static boolean identifierChar[] = { false, false, false,
0196:                    false, false, false, false, false, false, false, false,
0197:                    false, false, false, false, false, false, false, false,
0198:                    false, false, false, false, false, false, false, false,
0199:                    false, false, false, false, false, false, false, false,
0200:                    true, true, false, false, false, false, false, false,
0201:                    false, false, false, false, false, true, true, true, true,
0202:                    true, true, true, true, true, true, false, false, false,
0203:                    false, false, false, true, true, true, true, true, true,
0204:                    true, true, true, true, true, true, true, true, true, true,
0205:                    true, true, true, true, true, true, true, true, true, true,
0206:                    true, false, false, false, false, true, false, true, true,
0207:                    true, true, true, true, true, true, true, true, true, true,
0208:                    true, true, true, true, true, true, true, true, true, true,
0209:                    true, true, true, true, false, false, false, false, false };
0210:
0211:            /**
0212:             * Determines if character could be part of an SQL identifier.
0213:             * <p/>
0214:             * Characters > 127 are assumed to be unicode letters in other
0215:             * languages than english which is reasonable in this application.
0216:             * @param ch the character to test.
0217:             * @return <code>boolean</code> true if ch in A-Z a-z 0-9 @ $ # _.
0218:             */
0219:            private static boolean isIdentifier(int ch) {
0220:                return ch > 127 || identifierChar[ch];
0221:            }
0222:
0223:            /**
0224:             * Constructs a new parser object to process the supplied SQL.
0225:             *
0226:             * @param sqlIn     the SQL statement to parse
0227:             * @param paramList the parameter list array to populate or
0228:             *                  <code>null</code> if no parameters are expected
0229:             * @param connection the parent Connection object
0230:             */
0231:            private SQLParser(String sqlIn, ArrayList paramList,
0232:                    ConnectionJDBC2 connection) {
0233:                sql = sqlIn;
0234:                in = sql.toCharArray();
0235:                len = in.length;
0236:                out = new char[len + 256]; // Allow extra for curdate/curtime
0237:                params = paramList;
0238:                procName = "";
0239:
0240:                this .connection = connection;
0241:            }
0242:
0243:            /**
0244:             * Inserts a String literal in the output buffer.
0245:             *
0246:             * @param txt The text to insert.
0247:             */
0248:            private void copyLiteral(String txt) throws SQLException {
0249:                final int len = txt.length();
0250:
0251:                for (int i = 0; i < len; i++) {
0252:                    final char c = txt.charAt(i);
0253:
0254:                    if (c == '?') {
0255:                        if (params == null) {
0256:                            throw new SQLException(Messages.get(
0257:                                    "error.parsesql.unexpectedparam", String
0258:                                            .valueOf(s)), "2A000");
0259:                        }
0260:                        // param marker embedded in escape
0261:                        ParamInfo pi = new ParamInfo(d, connection
0262:                                .getUseUnicode());
0263:                        params.add(pi);
0264:                    }
0265:
0266:                    out[d++] = c;
0267:                }
0268:            }
0269:
0270:            /**
0271:             * Copies over an embedded string literal unchanged.
0272:             */
0273:            private void copyString() {
0274:                char saveTc = terminator;
0275:                char tc = in[s];
0276:
0277:                if (tc == '[') {
0278:                    tc = ']';
0279:                }
0280:
0281:                terminator = tc;
0282:
0283:                out[d++] = in[s++];
0284:
0285:                while (in[s] != tc) {
0286:                    out[d++] = in[s++];
0287:                }
0288:
0289:                out[d++] = in[s++];
0290:
0291:                terminator = saveTc;
0292:            }
0293:
0294:            /**
0295:             * Copies over possible SQL keyword eg 'SELECT'
0296:             */
0297:            private String copyKeyWord() {
0298:                int start = d;
0299:
0300:                while (s < len && isIdentifier(in[s])) {
0301:                    out[d++] = in[s++];
0302:                }
0303:
0304:                return String.valueOf(out, start, d - start).toLowerCase();
0305:            }
0306:
0307:            /**
0308:             * Builds a new parameter item.
0309:             *
0310:             * @param name Optional parameter name or null.
0311:             * @param pos The parameter marker position in the output buffer.
0312:             */
0313:            private void copyParam(String name, int pos) throws SQLException {
0314:                if (params == null) {
0315:                    throw new SQLException(Messages
0316:                            .get("error.parsesql.unexpectedparam", String
0317:                                    .valueOf(s)), "2A000");
0318:                }
0319:
0320:                ParamInfo pi = new ParamInfo(pos, connection.getUseUnicode());
0321:                pi.name = name;
0322:
0323:                if (pos >= 0) {
0324:                    out[d++] = in[s++];
0325:                } else {
0326:                    pi.isRetVal = true;
0327:                    s++;
0328:                }
0329:
0330:                params.add(pi);
0331:            }
0332:
0333:            /**
0334:             * Copies an embedded stored procedure identifier over to the output buffer.
0335:             *
0336:             * @return The identifier as a <code>String</code>.
0337:             */
0338:            private String copyProcName() throws SQLException {
0339:                int start = d;
0340:
0341:                do {
0342:                    if (in[s] == '"' || in[s] == '[') {
0343:                        copyString();
0344:                    } else {
0345:                        char c = in[s++];
0346:
0347:                        while (isIdentifier(c) || c == ';') {
0348:                            out[d++] = c;
0349:                            c = in[s++];
0350:                        }
0351:
0352:                        s--;
0353:                    }
0354:
0355:                    if (in[s] == '.') {
0356:                        while (in[s] == '.') {
0357:                            out[d++] = in[s++];
0358:                        }
0359:                    } else {
0360:                        break;
0361:                    }
0362:                } while (true);
0363:
0364:                if (d == start) {
0365:                    // Procedure name expected but found something else
0366:                    throw new SQLException(
0367:                            Messages.get("error.parsesql.syntax", "call",
0368:                                    String.valueOf(s)), "22025");
0369:                }
0370:
0371:                return new String(out, start, d - start);
0372:            }
0373:
0374:            /**
0375:             * Copies an embedded parameter name to the output buffer.
0376:             *
0377:             * @return The identifier as a <code>String</code>.
0378:             */
0379:            private String copyParamName() {
0380:                int start = d;
0381:                char c = in[s++];
0382:
0383:                while (isIdentifier(c)) {
0384:                    out[d++] = c;
0385:                    c = in[s++];
0386:                }
0387:
0388:                s--;
0389:
0390:                return new String(out, start, d - start);
0391:            }
0392:
0393:            /**
0394:             * Copies over white space.
0395:             */
0396:            private void copyWhiteSpace() {
0397:                while (s < in.length && Character.isWhitespace(in[s])) {
0398:                    out[d++] = in[s++];
0399:                }
0400:            }
0401:
0402:            /**
0403:             * Checks that the next character is as expected.
0404:             *
0405:             * @param c The expected character.
0406:             * @param copy True if found character should be copied.
0407:             * @throws SQLException if expected characeter not found.
0408:             */
0409:            private void mustbe(char c, boolean copy) throws SQLException {
0410:                if (in[s] != c) {
0411:                    throw new SQLException(Messages.get(
0412:                            "error.parsesql.mustbe", String.valueOf(s), String
0413:                                    .valueOf(c)), "22019");
0414:                }
0415:
0416:                if (copy) {
0417:                    out[d++] = in[s++];
0418:                } else {
0419:                    s++;
0420:                }
0421:            }
0422:
0423:            /**
0424:             * Skips embedded white space.
0425:             */
0426:            private void skipWhiteSpace() {
0427:                while (Character.isWhitespace(in[s])) {
0428:                    s++;
0429:                }
0430:            }
0431:
0432:            /**
0433:             * Skips single-line comments.
0434:             */
0435:            private void skipSingleComments() {
0436:                while (s < len && in[s] != '\n' && in[s] != '\r') {
0437:                    // comments should be passed on to the server
0438:                    out[d++] = in[s++];
0439:                }
0440:            }
0441:
0442:            /**
0443:             * Skips multi-line comments
0444:             */
0445:            private void skipMultiComments() throws SQLException {
0446:                int block = 0;
0447:
0448:                do {
0449:                    if (s < len - 1) {
0450:                        if (in[s] == '/' && in[s + 1] == '*') {
0451:                            block++;
0452:                        } else if (in[s] == '*' && in[s + 1] == '/') {
0453:                            block--;
0454:                        }
0455:                        // comments should be passed on to the server
0456:                        out[d++] = in[s++];
0457:                    } else {
0458:                        throw new SQLException(Messages.get(
0459:                                "error.parsesql.missing", "*/"), "22025");
0460:                    }
0461:                } while (block > 0);
0462:                out[d++] = in[s++];
0463:            }
0464:
0465:            /**
0466:             * Processes the JDBC {call procedure [(&#63;,&#63;,&#63;)]} type escape.
0467:             *
0468:             * @throws SQLException if an error occurs
0469:             */
0470:            private void callEscape() throws SQLException {
0471:                // Insert EXECUTE into SQL so that proc can be called as normal SQL
0472:                copyLiteral("EXECUTE ");
0473:                keyWord = "execute";
0474:                // Process procedure name
0475:                procName = copyProcName();
0476:                skipWhiteSpace();
0477:
0478:                if (in[s] == '(') { // Optional ( )
0479:                    s++;
0480:                    terminator = ')';
0481:                    skipWhiteSpace();
0482:                } else {
0483:                    terminator = '}';
0484:                }
0485:
0486:                out[d++] = ' ';
0487:
0488:                // Process any parameters
0489:                while (in[s] != terminator) {
0490:                    String name = null;
0491:
0492:                    if (in[s] == '@') {
0493:                        // Named parameter
0494:                        name = copyParamName();
0495:                        skipWhiteSpace();
0496:                        mustbe('=', true);
0497:                        skipWhiteSpace();
0498:
0499:                        if (in[s] == '?') {
0500:                            copyParam(name, d);
0501:                        } else {
0502:                            // Named param has literal value can't call as RPC
0503:                            procName = "";
0504:                        }
0505:                    } else if (in[s] == '?') {
0506:                        copyParam(name, d);
0507:                    } else {
0508:                        // Literal parameter can't call as RPC
0509:                        procName = "";
0510:                    }
0511:
0512:                    // Now find terminator or comma
0513:                    while (in[s] != terminator && in[s] != ',') {
0514:                        if (in[s] == '{') {
0515:                            escape();
0516:                        } else if (in[s] == '\'' || in[s] == '['
0517:                                || in[s] == '"') {
0518:                            copyString();
0519:                        } else {
0520:                            out[d++] = in[s++];
0521:                        }
0522:                    }
0523:
0524:                    if (in[s] == ',') {
0525:                        out[d++] = in[s++];
0526:                    }
0527:
0528:                    skipWhiteSpace();
0529:                }
0530:
0531:                if (terminator == ')') {
0532:                    s++; // Elide
0533:                }
0534:
0535:                terminator = '}';
0536:                skipWhiteSpace();
0537:            }
0538:
0539:            /**
0540:             * Utility routine to validate date and time escapes.
0541:             *
0542:             * @param mask The validation mask
0543:             * @return True if the escape was valid and processed OK.
0544:             */
0545:            private boolean getDateTimeField(byte[] mask) throws SQLException {
0546:                skipWhiteSpace();
0547:                if (in[s] == '?') {
0548:                    // Allow {ts ?} type construct
0549:                    copyParam(null, d);
0550:                    skipWhiteSpace();
0551:                    return in[s] == terminator;
0552:                }
0553:                out[d++] = '\'';
0554:                terminator = (in[s] == '\'' || in[s] == '"') ? in[s++] : '}';
0555:                skipWhiteSpace();
0556:                int ptr = 0;
0557:
0558:                while (ptr < mask.length) {
0559:                    char c = in[s++];
0560:                    if (c == ' ' && out[d - 1] == ' ') {
0561:                        continue; // Eliminate multiple spaces
0562:                    }
0563:
0564:                    if (mask[ptr] == '#') {
0565:                        if (!Character.isDigit(c)) {
0566:                            return false;
0567:                        }
0568:                    } else if (mask[ptr] != c) {
0569:                        return false;
0570:                    }
0571:
0572:                    if (c != '-') {
0573:                        out[d++] = c;
0574:                    }
0575:
0576:                    ptr++;
0577:                }
0578:
0579:                if (mask.length == 19) { // Timestamp
0580:                    int digits = 0;
0581:
0582:                    if (in[s] == '.') {
0583:                        out[d++] = in[s++];
0584:
0585:                        while (Character.isDigit(in[s])) {
0586:                            if (digits < 3) {
0587:                                out[d++] = in[s++];
0588:                                digits++;
0589:                            } else {
0590:                                s++;
0591:                            }
0592:                        }
0593:                    } else {
0594:                        out[d++] = '.';
0595:                    }
0596:
0597:                    for (; digits < 3; digits++) {
0598:                        out[d++] = '0';
0599:                    }
0600:                }
0601:
0602:                skipWhiteSpace();
0603:
0604:                if (in[s] != terminator) {
0605:                    return false;
0606:                }
0607:
0608:                if (terminator != '}') {
0609:                    s++; // Skip terminator
0610:                }
0611:
0612:                skipWhiteSpace();
0613:                out[d++] = '\'';
0614:
0615:                return true;
0616:            }
0617:
0618:            /** Syntax mask for time escape. */
0619:            private static final byte[] timeMask = { '#', '#', ':', '#', '#',
0620:                    ':', '#', '#' };
0621:
0622:            /** Syntax mask for date escape. */
0623:            private static final byte[] dateMask = { '#', '#', '#', '#', '-',
0624:                    '#', '#', '-', '#', '#' };
0625:
0626:            /** Syntax mask for timestamp escape. */
0627:            static final byte[] timestampMask = { '#', '#', '#', '#', '-', '#',
0628:                    '#', '-', '#', '#', ' ', '#', '#', ':', '#', '#', ':', '#',
0629:                    '#' };
0630:
0631:            /**
0632:             * Processes the JDBC escape {oj left outer join etc}.
0633:             *
0634:             * @throws SQLException
0635:             */
0636:            private void outerJoinEscape() throws SQLException {
0637:                while (in[s] != '}') {
0638:                    final char c = in[s];
0639:
0640:                    switch (c) {
0641:                    case '\'':
0642:                    case '"':
0643:                    case '[':
0644:                        copyString();
0645:                        break;
0646:                    case '{':
0647:                        // Nested escape!
0648:                        escape();
0649:                        break;
0650:                    case '?':
0651:                        copyParam(null, d);
0652:                        break;
0653:                    default:
0654:                        out[d++] = c;
0655:                        s++;
0656:                        break;
0657:                    }
0658:                }
0659:            }
0660:
0661:            /** Map of jdbc to sybase function names. */
0662:            private static HashMap fnMap = new HashMap();
0663:            /** Map of jdbc to sql server function names. */
0664:            private static HashMap msFnMap = new HashMap();
0665:            /** Map of jdbc to server data types for convert */
0666:            private static HashMap cvMap = new HashMap();
0667:
0668:            static {
0669:                // Microsoft only functions
0670:                msFnMap.put("length", "len($)");
0671:                msFnMap.put("truncate", "round($, 1)");
0672:                // Common functions
0673:                fnMap.put("user", "user_name($)");
0674:                fnMap.put("database", "db_name($)");
0675:                fnMap.put("ifnull", "isnull($)");
0676:                fnMap.put("now", "getdate($)");
0677:                fnMap.put("atan2", "atn2($)");
0678:                fnMap.put("mod", "($)");
0679:                fnMap.put("length", "char_length($)");
0680:                fnMap.put("locate", "charindex($)");
0681:                fnMap.put("repeat", "replicate($)");
0682:                fnMap.put("insert", "stuff($)");
0683:                fnMap.put("lcase", "lower($)");
0684:                fnMap.put("ucase", "upper($)");
0685:                fnMap.put("concat", "($)");
0686:                fnMap.put("curdate",
0687:                        "convert(datetime, convert(varchar, getdate(), 112))");
0688:                fnMap.put("curtime",
0689:                        "convert(datetime, convert(varchar, getdate(), 108))");
0690:                fnMap.put("dayname", "datename(weekday,$)");
0691:                fnMap.put("dayofmonth", "datepart(day,$)");
0692:                fnMap.put("dayofweek",
0693:                        "((datepart(weekday,$)+@@DATEFIRST-1)%7+1)");
0694:                fnMap.put("dayofyear", "datepart(dayofyear,$)");
0695:                fnMap.put("hour", "datepart(hour,$)");
0696:                fnMap.put("minute", "datepart(minute,$)");
0697:                fnMap.put("second", "datepart(second,$)");
0698:                fnMap.put("year", "datepart(year,$)");
0699:                fnMap.put("quarter", "datepart(quarter,$)");
0700:                fnMap.put("month", "datepart(month,$)");
0701:                fnMap.put("week", "datepart(week,$)");
0702:                fnMap.put("monthname", "datename(month,$)");
0703:                fnMap.put("timestampadd", "dateadd($)");
0704:                fnMap.put("timestampdiff", "datediff($)");
0705:                // convert jdbc to sql types
0706:                cvMap.put("binary", "varbinary");
0707:                cvMap.put("char", "varchar");
0708:                cvMap.put("date", "datetime");
0709:                cvMap.put("double", "float");
0710:                cvMap.put("longvarbinary", "image");
0711:                cvMap.put("longvarchar", "text");
0712:                cvMap.put("time", "datetime");
0713:                cvMap.put("timestamp", "timestamp");
0714:            }
0715:
0716:            /**
0717:             * Processes the JDBC escape {fn function()}.
0718:             *
0719:             * @throws SQLException
0720:             */
0721:            private void functionEscape() throws SQLException {
0722:                char tc = terminator;
0723:                skipWhiteSpace();
0724:                StringBuffer nameBuf = new StringBuffer();
0725:                //
0726:                // Capture name
0727:                //
0728:                while (isIdentifier(in[s])) {
0729:                    nameBuf.append(in[s++]);
0730:                }
0731:
0732:                String name = nameBuf.toString().toLowerCase();
0733:                //
0734:                // Now collect arguments
0735:                //
0736:                skipWhiteSpace();
0737:                mustbe('(', false);
0738:                int parenCnt = 1;
0739:                int argStart = d;
0740:                int arg2Start = 0;
0741:                terminator = ')';
0742:                while (in[s] != ')' || parenCnt > 1) {
0743:                    final char c = in[s];
0744:
0745:                    switch (c) {
0746:                    case '\'':
0747:                    case '"':
0748:                    case '[':
0749:                        copyString();
0750:                        break;
0751:                    case '{':
0752:                        // Process nested escapes!
0753:                        escape();
0754:                        break;
0755:                    case ',':
0756:                        if (arg2Start == 0) {
0757:                            arg2Start = d - argStart;
0758:                        }
0759:                        if ("concat".equals(name)) {
0760:                            out[d++] = '+';
0761:                            s++;
0762:                        } else if ("mod".equals(name)) {
0763:                            out[d++] = '%';
0764:                            s++;
0765:                        } else {
0766:                            out[d++] = c;
0767:                            s++;
0768:                        }
0769:                        break;
0770:                    case '(':
0771:                        parenCnt++;
0772:                        out[d++] = c;
0773:                        s++;
0774:                        break;
0775:                    case ')':
0776:                        parenCnt--;
0777:                        out[d++] = c;
0778:                        s++;
0779:                        break;
0780:                    default:
0781:                        out[d++] = c;
0782:                        s++;
0783:                        break;
0784:                    }
0785:                }
0786:
0787:                String args = String.valueOf(out, argStart, d - argStart)
0788:                        .trim();
0789:
0790:                d = argStart;
0791:                mustbe(')', false);
0792:                terminator = tc;
0793:                skipWhiteSpace();
0794:
0795:                //
0796:                // Process convert scalar function.
0797:                // Arguments need to be reversed and the data type
0798:                // argument converted to an SQL server type
0799:                //
0800:                if ("convert".equals(name) && arg2Start < args.length() - 1) {
0801:                    String arg2 = args.substring(arg2Start + 1).trim()
0802:                            .toLowerCase();
0803:                    String dataType = (String) cvMap.get(arg2);
0804:
0805:                    if (dataType == null) {
0806:                        // Will get server error if invalid type passed
0807:                        dataType = arg2;
0808:                    }
0809:
0810:                    copyLiteral("convert(");
0811:                    copyLiteral(dataType);
0812:                    out[d++] = ',';
0813:                    copyLiteral(args.substring(0, arg2Start));
0814:                    out[d++] = ')';
0815:
0816:                    return;
0817:                }
0818:
0819:                //
0820:                // See if function mapped
0821:                //
0822:                String fn;
0823:                if (connection.getServerType() == Driver.SQLSERVER) {
0824:                    fn = (String) msFnMap.get(name);
0825:                    if (fn == null) {
0826:                        fn = (String) fnMap.get(name);
0827:                    }
0828:                } else {
0829:                    fn = (String) fnMap.get(name);
0830:                }
0831:                if (fn == null) {
0832:                    // Not mapped so assume simple case
0833:                    copyLiteral(name);
0834:                    out[d++] = '(';
0835:                    copyLiteral(args);
0836:                    out[d++] = ')';
0837:                    return;
0838:                }
0839:                //
0840:                // Process timestamp interval constants
0841:                //
0842:                if (args.length() > 8
0843:                        && args.substring(0, 8).equalsIgnoreCase("sql_tsi_")) {
0844:                    args = args.substring(8);
0845:                    if (args.length() > 11
0846:                            && args.substring(0, 11).equalsIgnoreCase(
0847:                                    "frac_second")) {
0848:                        args = "millisecond" + args.substring(11);
0849:                    }
0850:                }
0851:                //
0852:                // Substitute mapped function name and arguments
0853:                //
0854:                final int len = fn.length();
0855:                for (int i = 0; i < len; i++) {
0856:                    final char c = fn.charAt(i);
0857:                    if (c == '$') {
0858:                        // Substitute arguments
0859:                        copyLiteral(args);
0860:                    } else {
0861:                        out[d++] = c;
0862:                    }
0863:                }
0864:            }
0865:
0866:            /**
0867:             * Processes the JDBC escape {escape 'X'}.
0868:             *
0869:             * @throws SQLException
0870:             */
0871:            private void likeEscape() throws SQLException {
0872:                copyLiteral("escape ");
0873:                skipWhiteSpace();
0874:
0875:                if (in[s] == '\'' || in[s] == '"') {
0876:                    copyString();
0877:                } else {
0878:                    mustbe('\'', true);
0879:                }
0880:
0881:                skipWhiteSpace();
0882:            }
0883:
0884:            /**
0885:             * Processes the JDBC escape sequences.
0886:             *
0887:             * @throws SQLException
0888:             */
0889:            private void escape() throws SQLException {
0890:                char tc = terminator;
0891:                terminator = '}';
0892:                StringBuffer escBuf = new StringBuffer();
0893:                s++;
0894:                skipWhiteSpace();
0895:
0896:                if (in[s] == '?') {
0897:                    copyParam("@return_status", -1);
0898:                    skipWhiteSpace();
0899:                    mustbe('=', false);
0900:                    skipWhiteSpace();
0901:
0902:                    while (Character.isLetter(in[s])) {
0903:                        escBuf.append(Character.toLowerCase(in[s++]));
0904:                    }
0905:
0906:                    skipWhiteSpace();
0907:                    String esc = escBuf.toString();
0908:
0909:                    if ("call".equals(esc)) {
0910:                        callEscape();
0911:                    } else {
0912:                        throw new SQLException(Messages.get(
0913:                                "error.parsesql.syntax", "call", String
0914:                                        .valueOf(s)), "22019");
0915:                    }
0916:                } else {
0917:                    while (Character.isLetter(in[s])) {
0918:                        escBuf.append(Character.toLowerCase(in[s++]));
0919:                    }
0920:
0921:                    skipWhiteSpace();
0922:                    String esc = escBuf.toString();
0923:
0924:                    if ("call".equals(esc)) {
0925:                        callEscape();
0926:                    } else if ("t".equals(esc)) {
0927:                        if (!getDateTimeField(timeMask)) {
0928:                            throw new SQLException(Messages.get(
0929:                                    "error.parsesql.syntax", "time", String
0930:                                            .valueOf(s)), "22019");
0931:                        }
0932:                    } else if ("d".equals(esc)) {
0933:                        if (!getDateTimeField(dateMask)) {
0934:                            throw new SQLException(Messages.get(
0935:                                    "error.parsesql.syntax", "date", String
0936:                                            .valueOf(s)), "22019");
0937:                        }
0938:                    } else if ("ts".equals(esc)) {
0939:                        if (!getDateTimeField(timestampMask)) {
0940:                            throw new SQLException(Messages.get(
0941:                                    "error.parsesql.syntax", "timestamp",
0942:                                    String.valueOf(s)), "22019");
0943:                        }
0944:                    } else if ("oj".equals(esc)) {
0945:                        outerJoinEscape();
0946:                    } else if ("fn".equals(esc)) {
0947:                        functionEscape();
0948:                    } else if ("escape".equals(esc)) {
0949:                        likeEscape();
0950:                    } else {
0951:                        throw new SQLException(Messages
0952:                                .get("error.parsesql.badesc", esc, String
0953:                                        .valueOf(s)), "22019");
0954:                    }
0955:                }
0956:
0957:                mustbe('}', false);
0958:                terminator = tc;
0959:            }
0960:
0961:            /**
0962:             * Extracts the first table name following the keyword FROM.
0963:             *
0964:             * @return the table name as a <code>String</code>
0965:             */
0966:            private String getTableName() throws SQLException {
0967:                StringBuffer name = new StringBuffer(128);
0968:                copyWhiteSpace();
0969:                char c = (s < len) ? in[s] : ' ';
0970:                if (c == '{') {
0971:                    // Start of {oj ... } we can assume that there is
0972:                    // more than one table in select and therefore
0973:                    // it would not be updateable.
0974:                    return "";
0975:                }
0976:                //
0977:                // Skip any leading comments before first table name
0978:                //
0979:                while (c == '/' || c == '-' && s + 1 < len) {
0980:                    if (c == '/') {
0981:                        if (in[s + 1] == '*') {
0982:                            skipMultiComments();
0983:                        } else {
0984:                            break;
0985:                        }
0986:                    } else {
0987:                        if (in[s + 1] == '-') {
0988:                            skipSingleComments();
0989:                        } else {
0990:                            break;
0991:                        }
0992:                    }
0993:                    copyWhiteSpace();
0994:                    c = (s < len) ? in[s] : ' ';
0995:                }
0996:
0997:                if (c == '{') {
0998:                    // See comment above
0999:                    return "";
1000:                }
1001:                //
1002:                // Now process table name
1003:                //
1004:                while (s < len) {
1005:                    if (c == '[' || c == '"') {
1006:                        int start = d;
1007:                        copyString();
1008:                        name.append(String.valueOf(out, start, d - start));
1009:                        copyWhiteSpace();
1010:                        c = (s < len) ? in[s] : ' ';
1011:                    } else {
1012:                        int start = d;
1013:                        c = (s < len) ? in[s++] : ' ';
1014:                        while ((isIdentifier(c)) && c != '.' && c != ',') {
1015:                            out[d++] = c;
1016:                            c = (s < len) ? in[s++] : ' ';
1017:                        }
1018:                        name.append(String.valueOf(out, start, d - start));
1019:                        s--;
1020:                        copyWhiteSpace();
1021:                        c = (s < len) ? in[s] : ' ';
1022:                    }
1023:                    if (c != '.') {
1024:                        break;
1025:                    }
1026:                    name.append(c);
1027:                    out[d++] = c;
1028:                    s++;
1029:                    copyWhiteSpace();
1030:                    c = (s < len) ? in[s] : ' ';
1031:                }
1032:                return name.toString();
1033:            }
1034:
1035:            /**
1036:             * Parses the SQL statement processing JDBC escapes and parameter markers.
1037:             *
1038:             * @param extractTable true to return the first table name in the FROM clause of a select
1039:             * @return The processed SQL statement, any procedure name, the first
1040:             * SQL keyword and (optionally) the first table name as elements 0 1, 2 and 3 of the
1041:             * returned <code>String[]</code>.
1042:             * @throws SQLException
1043:             */
1044:            String[] parse(boolean extractTable) throws SQLException {
1045:                boolean isSelect = false;
1046:                boolean isModified = false;
1047:                boolean isSlowScan = true;
1048:                try {
1049:                    while (s < len) {
1050:                        final char c = in[s];
1051:
1052:                        switch (c) {
1053:                        case '{':
1054:                            escape();
1055:                            isModified = true;
1056:                            break;
1057:                        case '[':
1058:                        case '"':
1059:                        case '\'':
1060:                            copyString();
1061:                            break;
1062:                        case '?':
1063:                            copyParam(null, d);
1064:                            break;
1065:                        case '/':
1066:                            if (s + 1 < len && in[s + 1] == '*') {
1067:                                skipMultiComments();
1068:                            } else {
1069:                                out[d++] = c;
1070:                                s++;
1071:                            }
1072:                            break;
1073:                        case '-':
1074:                            if (s + 1 < len && in[s + 1] == '-') {
1075:                                skipSingleComments();
1076:                            } else {
1077:                                out[d++] = c;
1078:                                s++;
1079:                            }
1080:                            break;
1081:                        default:
1082:                            if (isSlowScan && Character.isLetter(c)) {
1083:                                if (keyWord == null) {
1084:                                    keyWord = copyKeyWord();
1085:                                    if ("select".equals(keyWord)) {
1086:                                        isSelect = true;
1087:                                    }
1088:                                    isSlowScan = extractTable && isSelect;
1089:                                    break;
1090:                                }
1091:                                if (extractTable && isSelect) {
1092:                                    String sqlWord = copyKeyWord();
1093:                                    if ("from".equals(sqlWord)) {
1094:                                        // Ensure only first 'from' is processed
1095:                                        isSlowScan = false;
1096:                                        tableName = getTableName();
1097:                                    }
1098:                                    break;
1099:                                }
1100:                            }
1101:
1102:                            out[d++] = c;
1103:                            s++;
1104:                            break;
1105:                        }
1106:                    }
1107:
1108:                    //
1109:                    // Impose a reasonable maximum limit on the number of parameters
1110:                    // unless the connection is sending statements unprepared (i.e. by
1111:                    // building a plain query) and this is not a procedure call.
1112:                    //
1113:                    if (params != null && params.size() > 255
1114:                            && connection.getPrepareSql() != TdsCore.UNPREPARED
1115:                            && procName != null) {
1116:                        int limit = 255; // SQL 6.5 and Sybase < 12.50
1117:                        if (connection.getServerType() == Driver.SYBASE) {
1118:                            if (connection.getDatabaseMajorVersion() > 12
1119:                                    || connection.getDatabaseMajorVersion() == 12
1120:                                    && connection.getDatabaseMinorVersion() >= 50) {
1121:                                limit = 2000; // Actually 2048 but allow some head room
1122:                            }
1123:                        } else {
1124:                            if (connection.getDatabaseMajorVersion() == 7) {
1125:                                limit = 1000; // Actually 1024
1126:                            } else if (connection.getDatabaseMajorVersion() > 7) {
1127:                                limit = 2000; // Actually 2100
1128:                            }
1129:
1130:                        }
1131:                        if (params.size() > limit) {
1132:                            throw new SQLException(Messages.get(
1133:                                    "error.parsesql.toomanyparams", Integer
1134:                                            .toString(limit)), "22025");
1135:                        }
1136:                    }
1137:                    String result[] = new String[4];
1138:
1139:                    // return sql and procname
1140:                    result[0] = (isModified) ? new String(out, 0, d) : sql;
1141:                    result[1] = procName;
1142:                    result[2] = (keyWord == null) ? "" : keyWord;
1143:                    result[3] = tableName;
1144:                    return result;
1145:                } catch (IndexOutOfBoundsException e) {
1146:                    // Should only come here if string is invalid in some way.
1147:                    throw new SQLException(Messages.get(
1148:                            "error.parsesql.missing", String
1149:                                    .valueOf(terminator)), "22025");
1150:                }
1151:            }
1152:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.