// 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.SQLCacheKey; import net.sourceforge.jtds.jdbc.cache.SimpleLRUCache; /** * 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. * * @author * Mike Hutchinson, Holger Rehn */ 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; } } } /** * a LRU cache for the last 500 parsed SQL statements */ private final static SimpleLRUCache<SQLCacheKey,CachedSQLQuery> _Cache = new SimpleLRUCache( 1000 ); /** 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 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 JtdsConnection connection; /** * <p> Parse the SQL statement processing JDBC escapes and parameter markers. * </p> * * @param extractTable * {@code 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[]}. * * @throws SQLException * if a parse error occurs */ static String[] parse( String sql, ArrayList paramList, JtdsConnection connection, boolean extractTable ) throws SQLException { String[] ret; // don't cache extract table parse requests if( extractTable ) { ret = new SQLParser( sql, paramList, connection ).parse( extractTable ); } else { // 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. SQLCacheKey cacheKey = new SQLCacheKey( sql, connection ); CachedSQLQuery cachedQuery = _Cache.get( cacheKey ); if( cachedQuery == null ) { // parse statement ret = new SQLParser( sql, paramList, connection ).parse( extractTable ); // update LRU cache _Cache.put( cacheKey, new CachedSQLQuery( ret, paramList ) ); } else { ret = cachedQuery.parsedSql; // create ParamInfo objects from CachedSQLQuery int length = cachedQuery.paramNames == null ? 0 : cachedQuery.paramNames.length; for( int i = 0; i < length; i ++ ) { paramList.add( new ParamInfo( cachedQuery.paramNames[i], cachedQuery.paramMarkerPos[i], cachedQuery.paramIsRetVal[i], cachedQuery.paramIsUnicode[i] ) ); } } } return ret; } // --------------------------- Private Methods -------------------------------- /** 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, JtdsConnection connection) { sql = sqlIn; in = sql.toCharArray(); len = in.length; out = new char[len]; 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); } append(c); } } /** * Copies over an embedded string literal unchanged. */ private void copyString() { char saveTc = terminator; char tc = in[s]; if (tc == '[') { tc = ']'; } terminator = tc; append(in[s++]); while (in[s] != tc) { append(in[s++]); } append(in[s++]); terminator = saveTc; } /** * Copies over possible SQL keyword eg 'SELECT' */ private String copyKeyWord() { int start = d; while (s < len && isIdentifier(in[s])) { append(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) { append(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 == ';') { append(c); c = in[s++]; } s--; } if (in[s] == '.') { while (in[s] == '.') { append(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)) { append(c); c = in[s++]; } s--; return new String(out, start, d - start); } /** * Copies over white space. */ private void copyWhiteSpace() { while (s < in.length && Character.isWhitespace(in[s])) { append(in[s++]); } } /** * Checks that the next character is as expected. * * @param c The expected character. * @param copy True if found character should be copied. * @throws SQLException if expected characeter not found. */ private void mustbe(char c, boolean copy) throws SQLException { if (in[s] != c) { throw new SQLException( Messages.get("error.parsesql.mustbe", String.valueOf(s), String.valueOf(c)), "22019"); } if (copy) { append(in[s++]); } else { s++; } } private void skipWhiteSpace() throws SQLException { for( ; s < len; ) { // skip whitespace while( Character.isWhitespace( sql.charAt( s ) ) ) { // skip white space without copying it s ++; } // check for comments to copy switch( sql.charAt( s ) ) { case '-': // skip (and copy) single comment if( s + 1 < len && in[s + 1] == '-' ) { append( in[s ++] ); append( in[s ++] ); while( s < len && in[s] != '\n' && in[s] != '\r' ) { append( in[s ++] ); } } else { return; } break; case '/': // skip (and copy) multi line comment if( s + 1 < len && in[s + 1] == '*' ) { append( in[s ++] ); append( in[s ++] ); int level = 1; do { // ensure at least 2 chars available, otherwise */ cannot be found anymore if( s >= len -1 ) throw new SQLException( Messages.get( "error.parsesql.missing", "*/" ), "22025" ); if( in[s] == '/' && s + 1 < len && in[s + 1] == '*' ) { append( in[s ++] ); level ++; } else if( in[s] == '*' && s + 1 < len && in[s + 1] == '/' ) { append( in[s ++] ); level --; } append( in[s ++] ); } while( level > 0 ); } else { return; } break; default: return; } } } /** * Skips single-line comments. */ private void skipSingleComments() { while (s < len && in[s] != '\n' && in[s] != '\r') { // comments should be passed on to the server append(in[s++]); } } /** * Skips multi-line comments */ private void skipMultiComments() throws SQLException { int block = 0; do { if (s < len - 1) { if (in[s] == '/' && in[s + 1] == '*') { append(in[s++]); block++; } else if (in[s] == '*' && in[s + 1] == '/') { append(in[s++]); block--; } // comments should be passed on to the server append(in[s++]); } else { throw new SQLException( Messages.get("error.parsesql.missing", "*/"), "22025"); } } while (block > 0); } /** * Processes the JDBC {call procedure [(?,?,?)]} type escape. * * @throws SQLException if an error occurs */ private void callEscape() throws SQLException { // Insert EXECUTE into SQL so that proc can be called as normal SQL copyLiteral("EXECUTE "); keyWord = "execute"; // Process procedure name procName = copyProcName(); skipWhiteSpace(); if (in[s] == '(') { // Optional ( ) s++; terminator = ')'; skipWhiteSpace(); } else { terminator = '}'; } append(' '); // Process any parameters while (in[s] != terminator) { String name = null; if (in[s] == '@') { // Named parameter name = copyParamName(); skipWhiteSpace(); mustbe('=', true); skipWhiteSpace(); if (in[s] == '?') { copyParam(name, d); } else { // Named param has literal value can't call as RPC procName = ""; } } else if (in[s] == '?') { copyParam(name, d); } else { // Literal parameter can't call as RPC procName = ""; } skipWhiteSpace(); // Now find terminator or comma while (in[s] != terminator && in[s] != ',') { if (in[s] == '{') { escape(); } else if (in[s] == '\'' || in[s] == '[' || in[s] == '"') { copyString(); } else { append(in[s++]); } } if (in[s] == ',') { append(in[s++]); } skipWhiteSpace(); } if (terminator == ')') { s++; // Elide } terminator = '}'; skipWhiteSpace(); } /** * Utility routine to validate date and time escapes. * * @param mask The validation mask * @return True if the escape was valid and processed OK. */ private boolean getDateTimeField(byte[] mask) throws SQLException { skipWhiteSpace(); if (in[s] == '?') { // Allow {ts ?} type construct copyParam(null, d); skipWhiteSpace(); return in[s] == terminator; } // fix for bug #682, CONVERT not allowed in procedure or function calls boolean sel = keyWord.equals( "select" ); if( sel ) { append( "convert(datetime,".toCharArray() ); } append('\''); terminator = (in[s] == '\'' || in[s] == '"') ? in[s++] : '}'; skipWhiteSpace(); int ptr = 0; while (ptr < mask.length) { char c = in[s++]; if (c == ' ' && out[d - 1] == ' ') { continue; // Eliminate multiple spaces } if (mask[ptr] == '#') { if (!Character.isDigit(c)) { return false; } } else if (mask[ptr] != c) { return false; } if (c != '-') { append(c); } ptr++; } if (mask.length == 19) { // Timestamp int digits = 0; if (in[s] == '.') { append(in[s++]); while (Character.isDigit(in[s])) { if (digits < 3) { append(in[s++]); digits++; } else { s++; } } } else { append('.'); } for (; digits < 3; digits++) { append('0'); } } skipWhiteSpace(); if (in[s] != terminator) { return false; } if (terminator != '}') { s++; // Skip terminator } skipWhiteSpace(); append('\''); if( sel ) { append(')'); } return true; } /** Syntax mask for time escape. */ private static final byte[] timeMask = { '#','#',':','#','#',':','#','#' }; /** Syntax mask for date escape. */ private static final byte[] dateMask = { '#','#','#','#','-','#','#','-','#','#' }; /** Syntax mask for timestamp escape. */ static final byte[] timestampMask = { '#','#','#','#','-','#','#','-','#','#',' ', '#','#',':','#','#',':','#','#' }; /** * Processes the JDBC escape {oj left outer join etc}. * * @throws SQLException */ private void outerJoinEscape() throws SQLException { while (in[s] != '}') { final char c = in[s]; switch (c) { case '\'': case '"': case '[': copyString(); break; case '{': // Nested escape! escape(); break; case '?': copyParam(null, d); break; default: append(c); s++; break; } } } /** Map of jdbc to sybase function names. */ private static HashMap fnMap = new HashMap(); /** Map of jdbc to sql server function names. */ private static HashMap msFnMap = new HashMap(); /** Map of jdbc to server data types for convert */ private static HashMap cvMap = new HashMap(); static { // Microsoft only functions msFnMap.put("length", "len($)"); msFnMap.put("truncate", "round($, 1)"); // Common functions fnMap.put("user", "user_name($)"); fnMap.put("database", "db_name($)"); fnMap.put("ifnull", "isnull($)"); fnMap.put("now", "getdate($)"); fnMap.put("atan2", "atn2($)"); fnMap.put("mod", "($)"); fnMap.put("length", "char_length($)"); fnMap.put("locate", "charindex($)"); fnMap.put("repeat", "replicate($)"); fnMap.put("insert", "stuff($)"); fnMap.put("lcase", "lower($)"); fnMap.put("ucase", "upper($)"); fnMap.put("concat", "($)"); fnMap.put("curdate", "convert(datetime, convert(varchar, getdate(), 112))"); fnMap.put("curtime", "convert(datetime, convert(varchar, getdate(), 108))"); fnMap.put("dayname", "datename(weekday,$)"); fnMap.put("dayofmonth", "datepart(day,$)"); fnMap.put("dayofweek", "((datepart(weekday,$)+@@DATEFIRST-1)%7+1)"); fnMap.put("dayofyear", "datepart(dayofyear,$)"); fnMap.put("hour", "datepart(hour,$)"); fnMap.put("minute", "datepart(minute,$)"); fnMap.put("second", "datepart(second,$)"); fnMap.put("year", "datepart(year,$)"); fnMap.put("quarter", "datepart(quarter,$)"); fnMap.put("month", "datepart(month,$)"); fnMap.put("week", "datepart(week,$)"); fnMap.put("monthname", "datename(month,$)"); fnMap.put("timestampadd", "dateadd($)"); fnMap.put("timestampdiff", "datediff($)"); // convert jdbc to sql types cvMap.put("binary", "varbinary"); cvMap.put("char", "varchar"); cvMap.put("date", "datetime"); cvMap.put("double", "float"); cvMap.put("longvarbinary", "image"); cvMap.put("longvarchar", "text"); cvMap.put("time", "datetime"); cvMap.put("timestamp", "timestamp"); } /** * Processes the JDBC escape {fn function()}. * * @throws SQLException */ private void functionEscape() throws SQLException { char tc = terminator; skipWhiteSpace(); StringBuilder nameBuf = new StringBuilder(); // // Capture name // while (isIdentifier(in[s])) { nameBuf.append(in[s++]); } String name = nameBuf.toString().toLowerCase(); // // Now collect arguments // skipWhiteSpace(); mustbe('(', false); int parenCnt = 1; int argStart = d; int arg2Start = 0; terminator = ')'; while (in[s] != ')' || parenCnt > 1) { final char c = in[s]; switch (c) { case '\'': case '"': case '[': copyString(); break; case '{': // Process nested escapes! escape(); break; case ',': if( parenCnt == 1 ) { if( arg2Start == 0 ) { arg2Start = d - argStart; } if( "concat".equals( name ) ) { append( '+' ); s++; } else if( "mod".equals( name ) ) { append( '%' ); s++; } else { append( c ); s++; } } else { append( c ); s++; } break; case '(': parenCnt++; append(c); s++; break; case ')': parenCnt--; append(c); s++; break; default: append(c); s++; break; } } String args = String.valueOf(out, argStart, d - argStart).trim(); d = argStart; mustbe(')', false); terminator = tc; skipWhiteSpace(); // // Process convert scalar function. // Arguments need to be reversed and the data type // argument converted to an SQL server type // if ("convert".equals(name) && arg2Start < args.length() - 1) { String arg2 = args.substring(arg2Start + 1).trim().toLowerCase(); String dataType = (String) cvMap.get(arg2); if (dataType == null) { // Will get server error if invalid type passed dataType = arg2; } copyLiteral("convert("); copyLiteral(dataType); append(','); copyLiteral(args.substring(0, arg2Start)); append(')'); return; } // // See if function mapped // String fn; if (connection.getServerType() == Driver.SQLSERVER) { fn = (String) msFnMap.get(name); if (fn == null) { fn = (String) fnMap.get(name); } } else { fn = (String) fnMap.get(name); } if (fn == null) { // Not mapped so assume simple case copyLiteral(name); append('('); copyLiteral(args); append(')'); return; } // // Process timestamp interval constants // if (args.length() > 8 && args.substring(0, 8).equalsIgnoreCase("sql_tsi_")) { args = args.substring(8); if (args.length() > 11 && args.substring(0, 11).equalsIgnoreCase("frac_second")) { args = "millisecond" + args.substring(11); } } // // Substitute mapped function name and arguments // final int len = fn.length(); for (int i = 0; i < len; i++) { final char c = fn.charAt(i); if (c == '$') { // Substitute arguments copyLiteral(args); } else { append(c); } } } /** * Processes the JDBC escape {escape 'X'}. * * @throws SQLException */ private void likeEscape() throws SQLException { copyLiteral("escape "); skipWhiteSpace(); if (in[s] == '\'' || in[s] == '"') { copyString(); } else { mustbe('\'', true); } skipWhiteSpace(); } /** * Processes the JDBC escape sequences. * * @throws SQLException */ private void escape() throws SQLException { char tc = terminator; terminator = '}'; StringBuilder escBuf = new StringBuilder(); s++; skipWhiteSpace(); if (in[s] == '?') { copyParam("@return_status", -1); skipWhiteSpace(); mustbe('=', false); skipWhiteSpace(); while (Character.isLetter(in[s])) { escBuf.append(Character.toLowerCase(in[s++])); } skipWhiteSpace(); String esc = escBuf.toString(); if ("call".equals(esc)) { callEscape(); } else { throw new SQLException( Messages.get("error.parsesql.syntax", "call", String.valueOf(s)), "22019"); } } else { while (Character.isLetter(in[s])) { escBuf.append(Character.toLowerCase(in[s++])); } skipWhiteSpace(); String esc = escBuf.toString(); if ("call".equals(esc)) { callEscape(); } else if ("t".equals(esc)) { if (!getDateTimeField(timeMask)) { throw new SQLException( Messages.get("error.parsesql.syntax", "time", String.valueOf(s)), "22019"); } } else if ("d".equals(esc)) { if (!getDateTimeField(dateMask)) { throw new SQLException( Messages.get("error.parsesql.syntax", "date", String.valueOf(s)), "22019"); } } else if ("ts".equals(esc)) { if (!getDateTimeField(timestampMask)) { throw new SQLException( Messages.get("error.parsesql.syntax", "timestamp", String.valueOf(s)), "22019"); } } else if ("oj".equals(esc)) { outerJoinEscape(); } else if ("fn".equals(esc)) { functionEscape(); } else if ("escape".equals(esc)) { likeEscape(); } else { throw new SQLException( Messages.get("error.parsesql.badesc", esc, String.valueOf(s)), "22019"); } } mustbe('}', false); terminator = tc; } /** * Extracts the first table name following the keyword FROM. * * @return the table name as a <code>String</code> */ private String getTableName() throws SQLException { StringBuilder name = new StringBuilder(128); copyWhiteSpace(); char c = (s < len) ? in[s] : ' '; if (c == '{') { // Start of {oj ... } we can assume that there is // more than one table in select and therefore // it would not be updateable. return ""; } // // Skip any leading comments before first table name // while (c == '/' || c == '-' && s + 1 < len) { if (c == '/') { if (in[s + 1] == '*') { skipMultiComments(); } else { break; } } else { if (in[s + 1] == '-') { skipSingleComments(); } else { break; } } copyWhiteSpace(); c = (s < len) ? in[s] : ' '; } if (c == '{') { // See comment above return ""; } // // Now process table name // while (s < len) { if (c == '[' || c == '"') { int start = d; copyString(); name.append(String.valueOf(out, start, d - start)); copyWhiteSpace(); c = (s < len) ? in[s] : ' '; } else { int start = d; c = (s < len) ? in[s++] : ' '; while ((isIdentifier(c)) && c != '.' && c != ',') { append(c); c = (s < len) ? in[s++] : ' '; } name.append(String.valueOf(out, start, d - start)); s--; copyWhiteSpace(); c = (s < len) ? in[s] : ' '; } if (c != '.') { break; } name.append(c); append(c); s++; copyWhiteSpace(); c = (s < len) ? in[s] : ' '; } return name.toString(); } private final void append( char[] chars ) { for( char c : chars ) { append( c ); } } /** * <p> Adds the given character to {@link #out}, incrementing {@link #d} by * {@code 1} and expanding {@link #out} by a fixed number of characters if * necessary. </p> */ private final void append( char character ) { try { out[d ++] = character; } catch( ArrayIndexOutOfBoundsException e ) { // expand output array by a fixed amount char[] expanded = new char[out.length + 256]; System.arraycopy( out, 0, expanded, 0, out.length ); // out = expanded; out[d - 1] = character; } } /** * Parses 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 */ String[] parse(boolean extractTable) throws SQLException { boolean isSelect = false; boolean isModified = false; boolean isSlowScan = true; try { while (s < len) { final char c = in[s]; switch (c) { case '{': escape(); isModified = true; break; case '[': case '"': case '\'': copyString(); break; case '?': copyParam(null, d); break; case '/': if (s+1 < len && in[s+1] == '*') { skipMultiComments(); } else { append(c); s++; } break; case '-': if (s+1 < len && in[s+1] == '-') { skipSingleComments(); } else { append(c); s++; } break; default: if (isSlowScan && Character.isLetter(c)) { if (keyWord == null) { keyWord = copyKeyWord(); if ("select".equals(keyWord)) { isSelect = true; } isSlowScan = extractTable && isSelect; break; } if (extractTable && isSelect) { String sqlWord = copyKeyWord(); if ("from".equals(sqlWord)) { // Ensure only first 'from' is processed isSlowScan = false; tableName = getTableName(); } break; } } append(c); s++; break; } } // // Impose a reasonable maximum limit on the number of parameters // unless the connection is sending statements unprepared (i.e. by // building a plain query) and this is not a procedure call. // if (params != null && params.size() > 255 && connection.getPrepareSql() != TdsCore.UNPREPARED && procName != null) { int limit = 255; // SQL 6.5 and Sybase < 12.50 if (connection.getServerType() == Driver.SYBASE) { if (connection.getDatabaseMajorVersion() > 12 || connection.getDatabaseMajorVersion() == 12 && connection.getDatabaseMinorVersion() >= 50) { limit = 2000; // Actually 2048 but allow some head room } } else { if (connection.getDatabaseMajorVersion() == 7) { limit = 1000; // Actually 1024 } else if (connection.getDatabaseMajorVersion() > 7) { limit = 2000; // Actually 2100 } } if (params.size() > limit) { throw new SQLException( Messages.get("error.parsesql.toomanyparams", Integer.toString(limit)), "22025"); } } String result[] = new String[4]; // return sql and procname result[0] = (isModified) ? new String(out, 0, d) : sql; result[1] = procName; result[2] = (keyWord == null) ? "" : keyWord; result[3] = tableName; return result; } catch (IndexOutOfBoundsException e) { // Should only come here if string is invalid in some way. throw new SQLException( Messages.get("error.parsesql.missing", String.valueOf(terminator)), "22025"); } } }