📄 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.29 2007/07/08 17:28:23 bheineman 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.getUseUnicode());
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.getUseUnicode());
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 + -