📄 sqlparser.java
字号:
// jTDS JDBC Driver for Microsoft SQL Server and Sybase// Copyright (C) 2004 The jTDS Project//// This library is free software; you can redistribute it and/or// modify it under the terms of the GNU Lesser General Public// License as published by the Free Software Foundation; either// version 2.1 of the License, or (at your option) any later version.//// This library is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU// Lesser General Public License for more details.//// You should have received a copy of the GNU Lesser General Public// License along with this library; if not, write to the Free Software// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA//package net.sourceforge.jtds.jdbc;import java.sql.SQLException;import java.util.ArrayList;import java.util.HashMap;import net.sourceforge.jtds.jdbc.cache.SimpleLRUCache;import net.sourceforge.jtds.jdbc.cache.SQLCacheKey;/** * Process JDBC escape strings and parameter markers in the SQL string. * <p> * This code recognizes the following escapes: * <ol> * <li>Date {d 'yyyy-mm-dd'} * <li>Time {t 'hh:mm:ss'} * <li>Timestamp {ts 'yyyy-mm-dd hh:mm:ss.nnn'} * <li>ESCAPE {escape 'x'} * <li>Function {fn xxxx([arg,arg...])} * NB The concat(arg, arg) operator is converted to (arg + arg) * <li>OuterJoin {oj .....} * <li>Call {?=call proc [arg, arg...]} * or {call proc [arg, arg...]} * </ol> * Notes: * <ol> * <li>This code is designed to be as efficient as possible and as * result the validation done here is limited. * <li>SQL comments are parsed correctly thanks to code supplied by * Joel Fouse. * </ol> * * @author Mike Hutchinson * @version $Id: SQLParser.java,v 1.25 2005/05/25 09:24:03 alin_sinpalean Exp $ */class SQLParser { /** * Serialized version of a parsed SQL query (the value stored in the cache * for a parsed SQL). * <p/> * Holds the parsed SQL query and the names, positions and return value and * unicode flags for the parameters. */ private static class CachedSQLQuery { final String[] parsedSql; final String[] paramNames; final int[] paramMarkerPos; final boolean[] paramIsRetVal; final boolean[] paramIsUnicode; CachedSQLQuery(String[] parsedSql, ArrayList params) { this.parsedSql = parsedSql; if (params != null) { final int size = params.size(); paramNames = new String[size]; paramMarkerPos = new int[size]; paramIsRetVal = new boolean[size]; paramIsUnicode = new boolean[size]; for (int i = 0; i < size; i++) { ParamInfo paramInfo = (ParamInfo) params.get(i); paramNames[i] = paramInfo.name; paramMarkerPos[i] = paramInfo.markerPos; paramIsRetVal[i] = paramInfo.isRetVal; paramIsUnicode[i] = paramInfo.isUnicode; } } else { paramNames = null; paramMarkerPos = null; paramIsRetVal = null; paramIsUnicode = null; } } } /** LRU cache of previously parsed SQL */ private static SimpleLRUCache cache; /** Original SQL string */ private final String sql; /** Input buffer with SQL statement. */ private final char[] in; /** Current position in input buffer. */ private int s; /** Length of input buffer. */ private final int len; /** Output buffer to contain parsed SQL. */ private final char[] out; /** Current position in output buffer. */ private int d; /** * Parameter list to be populated or <code>null</code> if no parameters * are expected. */ private final ArrayList params; /** Current expected terminator character. */ private char terminator; /** Procedure name in call escape. */ private String procName; /** First SQL keyword or identifier in statement. */ private String keyWord; /** First table name in from clause */ private String tableName; /** Connection object for server specific parsing. */ private final ConnectionJDBC2 connection; /** * Parse the SQL statement processing JDBC escapes and parameter markers. * * @param extractTable * true to return the first table name in the FROM clause of a select * @return The processed SQL statement, any procedure name, the first SQL * keyword and (optionally) the first table name as * elements 0 1, 2 and 3 of the returned <code>String[]</code>. * @throws SQLException if a parse error occurs */ static String[] parse(String sql, ArrayList paramList, ConnectionJDBC2 connection, boolean extractTable) throws SQLException { // Don't cache extract table parse requests, just process it if (extractTable) { SQLParser parser = new SQLParser(sql, paramList, connection); return parser.parse(extractTable); } SimpleLRUCache cache = getCache(connection); SQLCacheKey cacheKey = new SQLCacheKey(sql, connection); // By not synchronizing on the cache, we're admitting that the possibility of multiple // parses of the same statement can occur. However, it is 1) unlikely under normal // usage, and 2) harmless to the cache. By avoiding a synchronization block around // the get()-parse()-put(), we reduce the contention greatly in the nominal case. CachedSQLQuery cachedQuery = (CachedSQLQuery) cache.get(cacheKey); if (cachedQuery == null) { // Parse and cache SQL SQLParser parser = new SQLParser(sql, paramList, connection); cachedQuery = new CachedSQLQuery(parser.parse(extractTable), paramList); cache.put(cacheKey, cachedQuery); } else { // Create full ParamInfo objects out of cached object final int length = (cachedQuery.paramNames == null) ? 0 : cachedQuery.paramNames.length; for (int i = 0; i < length; i++) { ParamInfo paramInfo = new ParamInfo(cachedQuery.paramNames[i], cachedQuery.paramMarkerPos[i], cachedQuery.paramIsRetVal[i], cachedQuery.paramIsUnicode[i]); paramList.add(paramInfo); } } return cachedQuery.parsedSql; } // --------------------------- Private Methods -------------------------------- /** * Retrieves the statement cache, creating it if required. * * @return the cache as a <code>SimpleLRUCache</code> */ private synchronized static SimpleLRUCache getCache(ConnectionJDBC2 connection) { if (cache == null) { int maxStatements = connection.getMaxStatements(); maxStatements = Math.max(0, maxStatements); maxStatements = Math.min(1000, maxStatements); cache = new SimpleLRUCache(maxStatements); } return cache; } /** Lookup table to test if character is part of an identifier. */ private static boolean identifierChar[] = { false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, true, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false }; /** * Determines if character could be part of an SQL identifier. * <p/> * Characters > 127 are assumed to be unicode letters in other * languages than english which is reasonable in this application. * @param ch the character to test. * @return <code>boolean</code> true if ch in A-Z a-z 0-9 @ $ # _. */ private static boolean isIdentifier(int ch) { return ch > 127 || identifierChar[ch]; } /** * Constructs a new parser object to process the supplied SQL. * * @param sqlIn the SQL statement to parse * @param paramList the parameter list array to populate or * <code>null</code> if no parameters are expected * @param connection the parent Connection object */ private SQLParser(String sqlIn, ArrayList paramList, ConnectionJDBC2 connection) { sql = sqlIn; in = sql.toCharArray(); len = in.length; out = new char[len + 256]; // Allow extra for curdate/curtime params = paramList; procName = ""; this.connection = connection; } /** * Inserts a String literal in the output buffer. * * @param txt The text to insert. */ private void copyLiteral(String txt) throws SQLException { final int len = txt.length(); for (int i = 0; i < len; i++) { final char c = txt.charAt(i); if (c == '?') { if (params == null) { throw new SQLException( Messages.get("error.parsesql.unexpectedparam", String.valueOf(s)), "2A000"); } // param marker embedded in escape ParamInfo pi = new ParamInfo(d, connection.isUseUnicode()); params.add(pi); } out[d++] = c; } } /** * Copies over an embedded string literal unchanged. */ private void copyString() { char saveTc = terminator; char tc = in[s]; if (tc == '[') { tc = ']'; } terminator = tc; out[d++] = in[s++]; while (in[s] != tc) { out[d++] = in[s++]; } out[d++] = in[s++]; terminator = saveTc; } /** * Copies over possible SQL keyword eg 'SELECT' */ private String copyKeyWord() { int start = d; while (s < len && isIdentifier(in[s])) { out[d++] = in[s++]; } return String.valueOf(out, start, d - start).toLowerCase(); } /** * Builds a new parameter item. * * @param name Optional parameter name or null. * @param pos The parameter marker position in the output buffer. */ private void copyParam(String name, int pos) throws SQLException { if (params == null) { throw new SQLException( Messages.get("error.parsesql.unexpectedparam", String.valueOf(s)), "2A000"); } ParamInfo pi = new ParamInfo(pos, connection.isUseUnicode()); pi.name = name; if (pos >= 0) { out[d++] = in[s++]; } else { pi.isRetVal = true; s++; } params.add(pi); } /** * Copies an embedded stored procedure identifier over to the output buffer. * * @return The identifier as a <code>String</code>. */ private String copyProcName() throws SQLException { int start = d; do { if (in[s] == '"' || in[s] == '[') { copyString(); } else { char c = in[s++]; while (isIdentifier(c) || c == ';') { out[d++] = c; c = in[s++]; } s--; } if (in[s] == '.') { while (in[s] == '.') { out[d++] = in[s++]; } } else { break; } } while (true); if (d == start) { // Procedure name expected but found something else throw new SQLException( Messages.get("error.parsesql.syntax", "call", String.valueOf(s)), "22025"); } return new String(out, start, d - start); } /** * Copies an embedded parameter name to the output buffer. * * @return The identifier as a <code>String</code>. */ private String copyParamName() { int start = d; char c = in[s++]; while (isIdentifier(c)) { out[d++] = c; c = in[s++];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -