/*
 * Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
 * available under the terms of the MIT License. See the LICENSE file in the project root for more information.
 */

package com.microsoft.sqlserver.jdbc;

import static com.microsoft.sqlserver.jdbc.SQLServerConnection.getCachedParsedSQL;
import static com.microsoft.sqlserver.jdbc.SQLServerConnection.parseAndCacheSQL;

import java.sql.BatchUpdateException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key;


Provides an implementation of java.sql.Statement JDBC Interface to assist in creating Statements against SQL Server. It also provides a number of base class implementation methods for the JDBC prepared statement and callable Statements. SQLServerStatement's basic role is to execute SQL statements and return update counts and resultset rows to the user application. Documentation for specific public methods that are undocumented can be found under Sun's standard JDBC documentation for class java.sql.Statement. Those methods are part of Sun's standard JDBC documentation and therefore their documentation is not duplicated here.

Implementation Notes

Fetching Result sets

The queries first rowset is available immediately after the executeQuery. The first rs.next() does not make a server round trip. For non server side resultsets the entire result set is in the rowset. For server side result sets the number of rows in the rowset is set with nFetchSize

The API javadoc for JDBC API methods that this class implements are not repeated here. Please see Sun's JDBC API interfaces javadoc for those details.

/** * Provides an implementation of java.sql.Statement JDBC Interface to assist in creating Statements against SQL Server. * It also provides a number of base class implementation methods for the JDBC prepared statement and callable * Statements. SQLServerStatement's basic role is to execute SQL statements and return update counts and resultset rows * to the user application. * * Documentation for specific public methods that are undocumented can be found under Sun's standard JDBC documentation * for class java.sql.Statement. Those methods are part of Sun's standard JDBC documentation and therefore their * documentation is not duplicated here. * <p> * Implementation Notes * <p> * Fetching Result sets * <p> * The queries first rowset is available immediately after the executeQuery. The first rs.next() does not make a server * round trip. For non server side resultsets the entire result set is in the rowset. For server side result sets the * number of rows in the rowset is set with nFetchSize * <p> * The API javadoc for JDBC API methods that this class implements are not repeated here. Please see Sun's JDBC API * interfaces javadoc for those details. */
public class SQLServerStatement implements ISQLServerStatement {
Always update serialVersionUID when prompted.
/** * Always update serialVersionUID when prompted. */
private static final long serialVersionUID = -4421134713913331507L; final static char LEFT_CURLY_BRACKET = 123; final static char RIGHT_CURLY_BRACKET = 125; private boolean isResponseBufferingAdaptive = false; final boolean getIsResponseBufferingAdaptive() { return isResponseBufferingAdaptive; } private boolean wasResponseBufferingSet = false; final boolean wasResponseBufferingSet() { return wasResponseBufferingSet; } final static String identityQuery = " select SCOPE_IDENTITY() AS GENERATED_KEYS";
the stored procedure name to call (if there is one)
/** the stored procedure name to call (if there is one) */
String procedureName;
OUT parameters associated with this statement's execution
/** OUT parameters associated with this statement's execution */
private int serverCursorId; final int getServerCursorId() { return serverCursorId; } private int serverCursorRowCount; final int getServerCursorRowCount() { return serverCursorRowCount; }
The value of the poolable state
/** The value of the poolable state */
boolean stmtPoolable;
Streaming access to the response TDS data stream
/** Streaming access to the response TDS data stream */
private TDSReader tdsReader; final TDSReader resultsReader() { return tdsReader; } final boolean wasExecuted() { return null != tdsReader; }
The input and out parameters for statement execution.
/** * The input and out parameters for statement execution. */
Parameter[] inOutParam; // Parameters for prepared stmts and stored procedures
The statement's connection.
/** * The statement's connection. */
final SQLServerConnection connection;
The user's specifed query timeout (in seconds).
/** * The user's specifed query timeout (in seconds). */
int queryTimeout;
timeout value for canceling the query timeout
/** * timeout value for canceling the query timeout */
int cancelQueryTimeoutSeconds;
Is closeOnCompletion is enabled? If true statement will be closed when all of its dependent result sets are closed
/** * Is closeOnCompletion is enabled? If true statement will be closed when all of its dependent result sets are * closed */
boolean isCloseOnCompletion = false;
Currently executing or most recently executed TDSCommand (statement cmd, server cursor cmd, ...) subject to cancellation through Statement.cancel. Note: currentCommand is declared volatile to ensure that the JVM always returns the most recently set value for currentCommand to the cancelling thread.
/** * Currently executing or most recently executed TDSCommand (statement cmd, server cursor cmd, ...) subject to * cancellation through Statement.cancel. * * Note: currentCommand is declared volatile to ensure that the JVM always returns the most recently set value for * currentCommand to the cancelling thread. */
private volatile TDSCommand currentCommand = null; private TDSCommand lastStmtExecCmd = null; final void discardLastExecutionResults() { if (null != lastStmtExecCmd && !bIsClosed) { lastStmtExecCmd.close(); lastStmtExecCmd = null; } clearLastResult(); } static final java.util.logging.Logger loggerExternal = java.util.logging.Logger .getLogger("com.microsoft.sqlserver.jdbc.Statement"); final private String loggingClassName; final private String traceID; String getClassNameLogging() { return loggingClassName; } /* * Column Encryption Override. Defaults to the connection setting, in which case it will be Enabled if * columnEncryptionSetting = true in the connection setting, Disabled if false. This may also be used to set other * behavior which overrides connection level setting. */ protected SQLServerStatementColumnEncryptionSetting stmtColumnEncriptionSetting = SQLServerStatementColumnEncryptionSetting.UseConnectionSetting; protected SQLServerStatementColumnEncryptionSetting getStmtColumnEncriptionSetting() { return stmtColumnEncriptionSetting; }
Encapsulates a subset of statement property values as they were set at execution time.
/** * Encapsulates a subset of statement property values as they were set at execution time. */
final class ExecuteProperties { final private boolean wasResponseBufferingSet; final boolean wasResponseBufferingSet() { return wasResponseBufferingSet; } final private boolean isResponseBufferingAdaptive; final boolean isResponseBufferingAdaptive() { return isResponseBufferingAdaptive; } final private int holdability; final int getHoldability() { return holdability; } ExecuteProperties(SQLServerStatement stmt) { wasResponseBufferingSet = stmt.wasResponseBufferingSet(); isResponseBufferingAdaptive = stmt.getIsResponseBufferingAdaptive(); holdability = stmt.connection.getHoldabilityInternal(); } } private ExecuteProperties execProps; final ExecuteProperties getExecProps() { return execProps; }
Executes this Statement using TDSCommand newStmtCmd. The TDSCommand is assumed to be a statement execution command (StmtExecCmd, PrepStmtExecCmd, PrepStmtBatchExecCmd).
/** * Executes this Statement using TDSCommand newStmtCmd. * * The TDSCommand is assumed to be a statement execution command (StmtExecCmd, PrepStmtExecCmd, * PrepStmtBatchExecCmd). */
final void executeStatement(TDSCommand newStmtCmd) throws SQLServerException, SQLTimeoutException { // Ensure that any response left over from a previous execution has been // completely processed. There may be ENVCHANGEs in that response that // we must acknowledge before proceeding. discardLastExecutionResults(); // make sure statement hasn't been closed due to closeOnCompletion checkClosed(); execProps = new ExecuteProperties(this); try { // (Re)execute this Statement with the new command executeCommand(newStmtCmd); } catch (SQLServerException e) { if (e.getDriverErrorCode() == SQLServerException.ERROR_QUERY_TIMEOUT) throw new SQLTimeoutException(e.getMessage(), e.getSQLState(), e.getErrorCode(), e.getCause()); else throw e; } finally { lastStmtExecCmd = newStmtCmd; } }
Executes TDSCommand newCommand through this Statement object, allowing it to be cancelled through Statement.cancel(). The specified command is typically the one used to execute the statement. But it could also be a server cursor command (fetch, move, close) generated by a ResultSet that this statement produced. This method does not prevent applications from simultaneously executing commands from multiple threads. The assumption is that apps only call cancel() from another thread while the command is executing.
/** * Executes TDSCommand newCommand through this Statement object, allowing it to be cancelled through * Statement.cancel(). * * The specified command is typically the one used to execute the statement. But it could also be a server cursor * command (fetch, move, close) generated by a ResultSet that this statement produced. * * This method does not prevent applications from simultaneously executing commands from multiple threads. The * assumption is that apps only call cancel() from another thread while the command is executing. */
final void executeCommand(TDSCommand newCommand) throws SQLServerException { // Set the new command as the current command so that // its execution can be cancelled from another thread currentCommand = newCommand; connection.executeCommand(newCommand); }
Flag to indicate that are potentially more results (ResultSets, update counts, or errors) to be processed in the response.
/** * Flag to indicate that are potentially more results (ResultSets, update counts, or errors) to be processed in the * response. */
boolean moreResults = false;
The statement's current result set.
/** * The statement's current result set. */
SQLServerResultSet resultSet;
The number of opened result sets in the statement.
/** * The number of opened result sets in the statement. */
int resultSetCount = 0;
Increment opened result set counter
/** * Increment opened result set counter */
synchronized void incrResultSetCount() { resultSetCount++; }
Decrement opened result set counter.
/** * Decrement opened result set counter. */
synchronized void decrResultSetCount() { resultSetCount--; assert resultSetCount >= 0; // close statement if no more result sets opened if (isCloseOnCompletion && !(EXECUTE_BATCH == executeMethod && moreResults) && resultSetCount == 0) { closeInternal(); } }
The statement execution method (executeQuery(), executeUpdate(), execute(), or executeBatch()) that was used to execute the statement.
/** * The statement execution method (executeQuery(), executeUpdate(), execute(), or executeBatch()) that was used to * execute the statement. */
static final int EXECUTE_NOT_SET = 0; static final int EXECUTE_QUERY = 1; static final int EXECUTE_UPDATE = 2; static final int EXECUTE = 3; static final int EXECUTE_BATCH = 4; static final int EXECUTE_QUERY_INTERNAL = 5; int executeMethod = EXECUTE_NOT_SET;
The update count returned from an update.
/** * The update count returned from an update. */
long updateCount = -1;
The status of escape processing.
/** * The status of escape processing. */
boolean escapeProcessing;
Limit for the maximum number of rows in a ResultSet
/** Limit for the maximum number of rows in a ResultSet */
int maxRows = 0; // default: 0 --> no limit
Limit for the size of data (in bytes) returned for any column value
/** Limit for the size of data (in bytes) returned for any column value */
int maxFieldSize = 0; // default: 0 --> no limit
The user's specified result set concurrency.
/** * The user's specified result set concurrency. */
int resultSetConcurrency;
The app result set type. This is the value passed to the statement's constructor (or inferred by default) when the statement was created. ResultSet.getType() returns this value. It may differ from the SQL Server result set type (see below). Namely, an app result set type of TYPE_FORWARD_ONLY will have an SQL Server result set type of TYPE_SS_DIRECT_FORWARD_ONLY or TYPE_SS_SERVER_CURSOR_FORWARD_ONLY depending on the value of the selectMethod connection property. Possible values of the app result set type are: TYPE_FORWARD_ONLY TYPE_SCROLL_INSENSITIVE TYPE_SCROLL_SENSITIVE TYPE_SS_DIRECT_FORWARD_ONLY TYPE_SS_SERVER_CURSOR_FORWARD_ONLY TYPE_SS_SCROLL_DYNAMIC TYPE_SS_SCROLL_KEYSET TYPE_SS_SCROLL_STATIC
/** * The app result set type. This is the value passed to the statement's constructor (or inferred by default) when * the statement was created. ResultSet.getType() returns this value. It may differ from the SQL Server result set * type (see below). Namely, an app result set type of TYPE_FORWARD_ONLY will have an SQL Server result set type of * TYPE_SS_DIRECT_FORWARD_ONLY or TYPE_SS_SERVER_CURSOR_FORWARD_ONLY depending on the value of the selectMethod * connection property. * * Possible values of the app result set type are: * * TYPE_FORWARD_ONLY TYPE_SCROLL_INSENSITIVE TYPE_SCROLL_SENSITIVE TYPE_SS_DIRECT_FORWARD_ONLY * TYPE_SS_SERVER_CURSOR_FORWARD_ONLY TYPE_SS_SCROLL_DYNAMIC TYPE_SS_SCROLL_KEYSET TYPE_SS_SCROLL_STATIC */
int appResultSetType;
The SQL Server result set type. This is the value used everywhere EXCEPT ResultSet.getType(). This value may or may not be the same as the app result set type (above). Possible values of the SQL Server result set type are: TYPE_SS_DIRECT_FORWARD_ONLY TYPE_SS_SERVER_CURSOR_FORWARD_ONLY TYPE_SS_SCROLL_DYNAMIC TYPE_SS_SCROLL_KEYSET TYPE_SS_SCROLL_STATIC
/** * The SQL Server result set type. This is the value used everywhere EXCEPT ResultSet.getType(). This value may or * may not be the same as the app result set type (above). * * Possible values of the SQL Server result set type are: * * TYPE_SS_DIRECT_FORWARD_ONLY TYPE_SS_SERVER_CURSOR_FORWARD_ONLY TYPE_SS_SCROLL_DYNAMIC TYPE_SS_SCROLL_KEYSET * TYPE_SS_SCROLL_STATIC */
int resultSetType; final int getSQLResultSetType() { return resultSetType; } final int getCursorType() { return getResultSetScrollOpt() & ~TDS.SCROLLOPT_PARAMETERIZED_STMT; }
Returns whether to request a server cursor when executing this statement. Executing a statement with execute() or executeQuery() requests a server cursor in all scrollability and updatability combinations except direct forward-only, read-only. Note that when execution requests a server cursor (i.e. this method returns true), there is no guarantee that SQL Server returns one. The variable executedSqlDirectly indicates whether SQL Server executed the query with a cursor or not.
Returns:true if statement execution requests a server cursor, false otherwise.
/** * Returns whether to request a server cursor when executing this statement. * * Executing a statement with execute() or executeQuery() requests a server cursor in all scrollability and * updatability combinations except direct forward-only, read-only. * * Note that when execution requests a server cursor (i.e. this method returns true), there is no guarantee that SQL * Server returns one. The variable executedSqlDirectly indicates whether SQL Server executed the query with a * cursor or not. * * @return true if statement execution requests a server cursor, false otherwise. */
final boolean isCursorable(int executeMethod) { return resultSetType != SQLServerResultSet.TYPE_SS_DIRECT_FORWARD_ONLY && (EXECUTE == executeMethod || EXECUTE_QUERY == executeMethod); }
Indicates whether SQL Server executed this statement with a cursor or not. When trying to execute a cursor-unfriendly statement with a server cursor, SQL Server may choose to execute the statement directly (i.e. as if no server cursor had been requested) rather than fail to execute the statement at all. We need to know when this happens so that if no rows are returned, we can tell whether the result is an empty result set or a cursored result set with rows to be fetched later.
/** * Indicates whether SQL Server executed this statement with a cursor or not. * * When trying to execute a cursor-unfriendly statement with a server cursor, SQL Server may choose to execute the * statement directly (i.e. as if no server cursor had been requested) rather than fail to execute the statement at * all. We need to know when this happens so that if no rows are returned, we can tell whether the result is an * empty result set or a cursored result set with rows to be fetched later. */
boolean executedSqlDirectly = false;
Indicates whether OUT parameters (cursor ID and row count) from cursorized execution of this statement are expected in the response. In most cases, except for severe errors, cursor OUT parameters are returned whenever a cursor is requested for statement execution. Even if SQL Server does not cursorize the statement as requested, these values are still present in the response and must be processed, even though their values are meaningless in that case.
/** * Indicates whether OUT parameters (cursor ID and row count) from cursorized execution of this statement are * expected in the response. * * In most cases, except for severe errors, cursor OUT parameters are returned whenever a cursor is requested for * statement execution. Even if SQL Server does not cursorize the statement as requested, these values are still * present in the response and must be processed, even though their values are meaningless in that case. */
boolean expectCursorOutParams; class StmtExecOutParamHandler extends TDSTokenHandler { StmtExecOutParamHandler() { super("StmtExecOutParamHandler"); } boolean onRetStatus(TDSReader tdsReader) throws SQLServerException { (new StreamRetStatus()).setFromTDS(tdsReader); return true; } boolean onRetValue(TDSReader tdsReader) throws SQLServerException { if (expectCursorOutParams) { Parameter param = new Parameter( Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection)); // Read the cursor ID param.skipRetValStatus(tdsReader); serverCursorId = param.getInt(tdsReader); param.skipValue(tdsReader, true); param = new Parameter(Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection)); // Read the row count (-1 means unknown) param.skipRetValStatus(tdsReader); if (-1 == (serverCursorRowCount = param.getInt(tdsReader))) serverCursorRowCount = SQLServerResultSet.UNKNOWN_ROW_COUNT; param.skipValue(tdsReader, true); // We now have everything we need to build the result set. expectCursorOutParams = false; return true; } return false; } boolean onDone(TDSReader tdsReader) throws SQLServerException { return false; } }
The cursor name.
/** * The cursor name. */
String cursorName;
The user's specified fetch size. Only used for server side result sets. Client side cursors read all rows.
/** * The user's specified fetch size. Only used for server side result sets. Client side cursors read all rows. */
int nFetchSize; int defaultFetchSize;
The users specified result set fetch direction
/** * The users specified result set fetch direction */
int nFetchDirection;
True is the statement is closed
/** * True is the statement is closed */
boolean bIsClosed;
True if the user requested to driver to generate insert keys
/** * True if the user requested to driver to generate insert keys */
boolean bRequestedGeneratedKeys;
The result set if auto generated keys were requested.
/** * The result set if auto generated keys were requested. */
private ResultSet autoGeneratedKeys; /** * The array of objects in a batched call. Applicable to statements and prepared statements When the * iterativeBatching property is turned on. */
The buffer that accumulates batchable statements
/** The buffer that accumulates batchable statements */
private final ArrayList<String> batchStatementBuffer = new ArrayList<>();
logging init at the construction
/** logging init at the construction */
static final private java.util.logging.Logger stmtlogger = java.util.logging.Logger .getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerStatement");
Returns the statement's id for logging info
/** Returns the statement's id for logging info */
@Override public String toString() { return traceID; } // Internal function used in tracing String getClassNameInternal() { return "SQLServerStatement"; }
Generate the statement's logging ID
/** Generate the statement's logging ID */
private static final AtomicInteger lastStatementID = new AtomicInteger(0); private static int nextStatementID() { return lastStatementID.incrementAndGet(); }
Constructs a SQLServerStatement
Params:
  • con – The statements connections.
  • nType – The statement type.
  • nConcur – The statement concurrency.
  • stmtColEncSetting – The statement column encryption setting.
Throws:
/** * Constructs a SQLServerStatement * * @param con * The statements connections. * @param nType * The statement type. * @param nConcur * The statement concurrency. * @param stmtColEncSetting * The statement column encryption setting. * @exception SQLServerException * The statement could not be created. */
SQLServerStatement(SQLServerConnection con, int nType, int nConcur, SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException { // Return a string representation of this statement's unqualified class name // (e.g. "SQLServerStatement" or "SQLServerPreparedStatement"), // its unique ID, and its parent connection. int statementID = nextStatementID(); String classN = getClassNameInternal(); traceID = classN + ":" + statementID; loggingClassName = "com.microsoft.sqlserver.jdbc." + classN + ":" + statementID; stmtPoolable = false; connection = con; bIsClosed = false; // Validate result set type ... if (ResultSet.TYPE_FORWARD_ONLY != nType && ResultSet.TYPE_SCROLL_SENSITIVE != nType && ResultSet.TYPE_SCROLL_INSENSITIVE != nType && SQLServerResultSet.TYPE_SS_DIRECT_FORWARD_ONLY != nType && SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY != nType && SQLServerResultSet.TYPE_SS_SCROLL_DYNAMIC != nType && SQLServerResultSet.TYPE_SS_SCROLL_KEYSET != nType && SQLServerResultSet.TYPE_SS_SCROLL_STATIC != nType) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_unsupportedCursor"), null, true); } // ... and concurrency if (ResultSet.CONCUR_READ_ONLY != nConcur && ResultSet.CONCUR_UPDATABLE != nConcur && SQLServerResultSet.CONCUR_SS_SCROLL_LOCKS != nConcur && SQLServerResultSet.CONCUR_SS_OPTIMISTIC_CC != nConcur && SQLServerResultSet.CONCUR_SS_OPTIMISTIC_CCVAL != nConcur) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_unsupportedConcurrency"), null, true); } if (null == stmtColEncSetting) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_unsupportedStmtColEncSetting"), null, true); } stmtColumnEncriptionSetting = stmtColEncSetting; resultSetConcurrency = nConcur; // App result set type is always whatever was used to create the statement. // This value will be returned by ResultSet.getType() calls. appResultSetType = nType; // SQL Server result set type is used everwhere other than ResultSet.getType() and // may need to be inferred based on the app result set type and the value of // the selectMethod connection property. if (ResultSet.TYPE_FORWARD_ONLY == nType) { if (ResultSet.CONCUR_READ_ONLY == nConcur) { // Check selectMethod and set to TYPE_SS_DIRECT_FORWARD_ONLY or // TYPE_SS_SERVER_CURSOR_FORWARD_ONLY accordingly. String selectMethod = con.getSelectMethod(); resultSetType = (null == selectMethod || !"cursor".equals(selectMethod)) ? SQLServerResultSet.TYPE_SS_DIRECT_FORWARD_ONLY : // Default // forward-only, // read-only // cursor // type SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY; } else { resultSetType = SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY; } } else if (ResultSet.TYPE_SCROLL_INSENSITIVE == nType) { resultSetType = SQLServerResultSet.TYPE_SS_SCROLL_STATIC; } else if (ResultSet.TYPE_SCROLL_SENSITIVE == nType) { resultSetType = SQLServerResultSet.TYPE_SS_SCROLL_KEYSET; } else // App specified one of the SQL Server types { resultSetType = nType; } // Figure out default fetch direction nFetchDirection = (SQLServerResultSet.TYPE_SS_DIRECT_FORWARD_ONLY == resultSetType || SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY == resultSetType) ? ResultSet.FETCH_FORWARD : ResultSet.FETCH_UNKNOWN; // Figure out fetch size: // // Too many scroll locks adversely impact concurrency on the server // and thus system performance, so default to a low value when using // scroll locks. Otherwise, use a larger fetch size. nFetchSize = (SQLServerResultSet.CONCUR_SS_SCROLL_LOCKS == resultSetConcurrency) ? 8 : 128; // Save off the default fetch size so it can be restored by setFetchSize(0). defaultFetchSize = nFetchSize; // Check compatibility between the result set type and concurrency. // Against a Yukon server, certain scrollability values are incompatible // with all but read only concurrency. Against a Shiloh server, such // combinations cause the cursor to be silently "upgraded" to one that // works. We enforce the more restrictive behavior of the two here. if (ResultSet.CONCUR_READ_ONLY != nConcur && (SQLServerResultSet.TYPE_SS_DIRECT_FORWARD_ONLY == resultSetType || SQLServerResultSet.TYPE_SS_SCROLL_STATIC == resultSetType)) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_unsupportedCursorAndConcurrency"), null, true); } // All result set types other than firehose (SQL Server default) use server side cursors. setResponseBuffering(connection.getResponseBuffering()); setDefaultQueryTimeout(); setDefaultQueryCancelTimeout(); if (stmtlogger.isLoggable(java.util.logging.Level.FINER)) { stmtlogger.finer("Properties for " + toString() + ":" + " Result type:" + appResultSetType + " (" + resultSetType + ")" + " Concurrency:" + resultSetConcurrency + " Fetchsize:" + nFetchSize + " bIsClosed:" + bIsClosed + " useLastUpdateCount:" + connection.useLastUpdateCount()); } if (stmtlogger.isLoggable(java.util.logging.Level.FINE)) { stmtlogger.fine(toString() + " created by (" + connection.toString() + ")"); } } private void setDefaultQueryCancelTimeout() { int cancelQueryTimeoutSeconds = this.connection.getCancelQueryTimeoutSeconds(); if (cancelQueryTimeoutSeconds > 0) { this.cancelQueryTimeoutSeconds = cancelQueryTimeoutSeconds; } } // add query timeout to statement private void setDefaultQueryTimeout() { int queryTimeoutSeconds = this.connection.getQueryTimeoutSeconds(); if (queryTimeoutSeconds > 0) { this.queryTimeout = queryTimeoutSeconds; } } final java.util.logging.Logger getStatementLogger() { return stmtlogger; }
Closes the statement. Note that the public close() method performs all of the cleanup work through this internal method which cannot throw any exceptions. This is done deliberately to ensure that ALL of the object's client-side and server-side state is cleaned up as best as possible, even under conditions which would normally result in exceptions being thrown.
/** * Closes the statement. * * Note that the public close() method performs all of the cleanup work through this internal method which cannot * throw any exceptions. This is done deliberately to ensure that ALL of the object's client-side and server-side * state is cleaned up as best as possible, even under conditions which would normally result in exceptions being * thrown. */
void closeInternal() { // Regardless what happens when cleaning up, // the statement is considered closed. assert !bIsClosed; discardLastExecutionResults(); bIsClosed = true; autoGeneratedKeys = null; sqlWarnings = null; inOutParam = null; connection.removeOpenStatement(this); } @Override public void close() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "close"); if (!bIsClosed) closeInternal(); loggerExternal.exiting(getClassNameLogging(), "close"); } @Override public void closeOnCompletion() throws SQLException { loggerExternal.entering(getClassNameLogging(), "closeOnCompletion"); checkClosed(); // enable closeOnCompletion feature isCloseOnCompletion = true; loggerExternal.exiting(getClassNameLogging(), "closeOnCompletion"); } @Override public java.sql.ResultSet executeQuery(String sql) throws SQLServerException, SQLTimeoutException { loggerExternal.entering(getClassNameLogging(), "executeQuery", sql); if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } checkClosed(); executeStatement(new StmtExecCmd(this, sql, EXECUTE_QUERY, NO_GENERATED_KEYS)); loggerExternal.exiting(getClassNameLogging(), "executeQuery", resultSet); return resultSet; } final SQLServerResultSet executeQueryInternal(String sql) throws SQLServerException, SQLTimeoutException { checkClosed(); executeStatement(new StmtExecCmd(this, sql, EXECUTE_QUERY_INTERNAL, NO_GENERATED_KEYS)); return resultSet; } @Override public int executeUpdate(String sql) throws SQLServerException, SQLTimeoutException { loggerExternal.entering(getClassNameLogging(), "executeUpdate", sql); if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } checkClosed(); executeStatement(new StmtExecCmd(this, sql, EXECUTE_UPDATE, NO_GENERATED_KEYS)); // this shouldn't happen, caller probably meant to call executeLargeUpdate if (updateCount < Integer.MIN_VALUE || updateCount > Integer.MAX_VALUE) SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_updateCountOutofRange"), null, true); loggerExternal.exiting(getClassNameLogging(), "executeUpdate", updateCount); return (int) updateCount; } @Override public long executeLargeUpdate(String sql) throws SQLServerException, SQLTimeoutException { loggerExternal.entering(getClassNameLogging(), "executeLargeUpdate", sql); if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } checkClosed(); executeStatement(new StmtExecCmd(this, sql, EXECUTE_UPDATE, NO_GENERATED_KEYS)); loggerExternal.exiting(getClassNameLogging(), "executeLargeUpdate", updateCount); return updateCount; } @Override public boolean execute(String sql) throws SQLServerException, SQLTimeoutException { loggerExternal.entering(getClassNameLogging(), "execute", sql); if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } checkClosed(); executeStatement(new StmtExecCmd(this, sql, EXECUTE, NO_GENERATED_KEYS)); loggerExternal.exiting(getClassNameLogging(), "execute", null != resultSet); return null != resultSet; } private final class StmtExecCmd extends TDSCommand {
Always update serialVersionUID when prompted.
/** * Always update serialVersionUID when prompted. */
private static final long serialVersionUID = 4534132352812876292L; final SQLServerStatement stmt; final String sql; final int executeMethod; final int autoGeneratedKeys; StmtExecCmd(SQLServerStatement stmt, String sql, int executeMethod, int autoGeneratedKeys) { super(stmt.toString() + " executeXXX", stmt.queryTimeout, stmt.cancelQueryTimeoutSeconds); this.stmt = stmt; this.sql = sql; this.executeMethod = executeMethod; this.autoGeneratedKeys = autoGeneratedKeys; } final boolean doExecute() throws SQLServerException { stmt.doExecuteStatement(this); return false; } final void processResponse(TDSReader tdsReader) throws SQLServerException { ensureExecuteResultsReader(tdsReader); processExecuteResults(); } } private String ensureSQLSyntax(String sql) throws SQLServerException { if (sql.indexOf(LEFT_CURLY_BRACKET) >= 0) { CityHash128Key cacheKey = new CityHash128Key(sql); // Check for cached SQL metadata. ParsedSQLCacheItem cacheItem = getCachedParsedSQL(cacheKey); if (null == cacheItem) cacheItem = parseAndCacheSQL(cacheKey, sql); // Retrieve from cache item. procedureName = cacheItem.procedureName; return cacheItem.processedSQL; } return sql; } void startResults() { moreResults = true; } final void setMaxRowsAndMaxFieldSize() throws SQLServerException { if (EXECUTE_QUERY == executeMethod || EXECUTE == executeMethod) { connection.setMaxRows(maxRows); connection.setMaxFieldSize(maxFieldSize); } else { assert EXECUTE_UPDATE == executeMethod || EXECUTE_BATCH == executeMethod || EXECUTE_QUERY_INTERNAL == executeMethod; // If we are executing via any of the above methods then make sure any // previous maxRows limitation on the connection is removed. connection.setMaxRows(0); } } final void doExecuteStatement(StmtExecCmd execCmd) throws SQLServerException { resetForReexecute(); // Set this command as the current command executeMethod = execCmd.executeMethod; // Apps can use JDBC call syntax to call unparameterized stored procedures // through regular Statement objects. We need to ensure that any such JDBC // call syntax is rewritten here as SQL exec syntax. String sql = ensureSQLSyntax(execCmd.sql); if (!isInternalEncryptionQuery && connection.isAEv2()) { execCmd.enclaveCEKs = connection.initEnclaveParameters(sql, null, null, null); } // If this request might be a query (as opposed to an update) then make // sure we set the max number of rows and max field size for any ResultSet // that may be returned. // // If the app uses Statement.execute to execute an UPDATE or DELETE statement // and has called Statement.setMaxRows to limit the number of rows from an // earlier query, then the number of rows updated/deleted will be limited as // well. // // Note: similar logic in SQLServerPreparedStatement.doExecutePreparedStatement setMaxRowsAndMaxFieldSize(); if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } if (isCursorable(executeMethod) && isSelect(sql)) { if (stmtlogger.isLoggable(java.util.logging.Level.FINE)) stmtlogger.fine(toString() + " Executing server side cursor " + sql); doExecuteCursored(execCmd, sql); } else // Non-cursored execution (includes EXECUTE_QUERY_INTERNAL) { executedSqlDirectly = true; expectCursorOutParams = false; TDSWriter tdsWriter = execCmd.startRequest(TDS.PKT_QUERY); tdsWriter.sendEnclavePackage(sql, execCmd.enclaveCEKs); tdsWriter.writeString(sql); // If this is an INSERT statement and generated keys were requested // then add on the query to return them. if (RETURN_GENERATED_KEYS == execCmd.autoGeneratedKeys && (EXECUTE_UPDATE == executeMethod || EXECUTE == executeMethod) && sql.trim().toUpperCase().startsWith("INSERT")) { tdsWriter.writeString(identityQuery); } if (stmtlogger.isLoggable(java.util.logging.Level.FINE)) stmtlogger.fine(toString() + " Executing (not server cursor) " + sql); // Start the response ensureExecuteResultsReader(execCmd.startResponse(isResponseBufferingAdaptive)); startResults(); getNextResult(true); } // If execution produced no result set, then throw an exception if executeQuery() was used. if (null == resultSet) { if (EXECUTE_QUERY == executeMethod) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_noResultset"), null, true); } } // Otherwise, if execution produced a result set, then throw an exception // if executeUpdate() or executeBatch() was used. else { if (EXECUTE_UPDATE == executeMethod || EXECUTE_BATCH == executeMethod) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_resultsetGeneratedForUpdate"), null, false); } } } private final class StmtBatchExecCmd extends TDSCommand {
Always update serialVersionUID when prompted.
/** * Always update serialVersionUID when prompted. */
private static final long serialVersionUID = -4621631860790243331L; final SQLServerStatement stmt; StmtBatchExecCmd(SQLServerStatement stmt) { super(stmt.toString() + " executeBatch", stmt.queryTimeout, stmt.cancelQueryTimeoutSeconds); this.stmt = stmt; } final boolean doExecute() throws SQLServerException { stmt.doExecuteStatementBatch(this); return false; } final void processResponse(TDSReader tdsReader) throws SQLServerException { ensureExecuteResultsReader(tdsReader); processExecuteResults(); } } private void doExecuteStatementBatch(StmtBatchExecCmd execCmd) throws SQLServerException { resetForReexecute(); // Make sure any previous maxRows limitation on the connection is removed. connection.setMaxRows(0); String batchStatementString = String.join(";", batchStatementBuffer); if (connection.isAEv2()) { execCmd.enclaveCEKs = connection.initEnclaveParameters(batchStatementString, null, null, null); } if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } // Batch execution is always non-cursored executeMethod = EXECUTE_BATCH; executedSqlDirectly = true; expectCursorOutParams = false; TDSWriter tdsWriter = execCmd.startRequest(TDS.PKT_QUERY); // Write the concatenated batch of statements, delimited by semicolons tdsWriter.sendEnclavePackage(batchStatementString, execCmd.enclaveCEKs); tdsWriter.writeString(batchStatementString); // Start the response ensureExecuteResultsReader(execCmd.startResponse(isResponseBufferingAdaptive)); startResults(); getNextResult(true); // If execution produced a result set, then throw an exception if (null != resultSet) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_resultsetGeneratedForUpdate"), null, false); } }
Resets the state to get the statement for reexecute callable statement overrides this.
/** * Resets the state to get the statement for reexecute callable statement overrides this. */
final void resetForReexecute() throws SQLServerException { ensureExecuteResultsReader(null); autoGeneratedKeys = null; updateCount = -1; sqlWarnings = null; executedSqlDirectly = false; startResults(); }
Determines if the SQL is a SELECT.
Params:
  • sql – The statement SQL.
Returns:True if the statement is a select.
/** * Determines if the SQL is a SELECT. * * @param sql * The statement SQL. * @return True if the statement is a select. */
final boolean isSelect(String sql) throws SQLServerException { checkClosed(); // Used to check just the first letter which would cause // "Set" commands to return true... String temp = sql.trim(); if (null == sql || sql.length() < 6) { return false; } return "select".equalsIgnoreCase(temp.substring(0, 6)); }
Determine if the SQL is a INSERT.
Params:
  • sql – The statement SQL.
Returns:True if the statement is an insert.
/** * Determine if the SQL is a INSERT. * * @param sql * The statement SQL. * @return True if the statement is an insert. */
final boolean isInsert(String sql) throws SQLServerException { checkClosed(); // Used to check just the first letter which would cause // "Set" commands to return true... String temp = sql.trim(); if (null == sql || sql.length() < 6) { return false; } if ("/*".equalsIgnoreCase(temp.substring(0, 2))) { int index = temp.indexOf("*/") + 2; return isInsert(temp.substring(index)); } return "insert".equalsIgnoreCase(temp.substring(0, 6)); }
Replaces a JDBC parameter marker with the parameter's string value
Params:
  • str – the parameter syntax
  • marker – the parameter marker
  • replaceStr – the param value
Returns:String
/** * Replaces a JDBC parameter marker with the parameter's string value * * @param str * the parameter syntax * @param marker * the parameter marker * @param replaceStr * the param value * @return String */
static String replaceParameterWithString(String str, char marker, String replaceStr) { int index = 0; while ((index = str.indexOf("" + marker)) >= 0) { str = str.substring(0, index) + replaceStr + str.substring(index + 1, str.length()); } return str; }
Sets a JDBC parameter to null. (Used only when processing LOB column sources.)
Params:
  • sql – the parameter syntax
Returns:the result
/** * Sets a JDBC parameter to null. (Used only when processing LOB column sources.) * * @param sql * the parameter syntax * @return the result */
static String replaceMarkerWithNull(String sql) { if (!sql.contains("'")) { return replaceParameterWithString(sql, '?', "null"); } else { StringTokenizer st = new StringTokenizer(sql, "'", true); boolean beforeColon = true; final StringBuilder retSql = new StringBuilder(); while (st.hasMoreTokens()) { String str = st.nextToken(); if ("'".equals(str)) { retSql.append("'"); beforeColon = !beforeColon; continue; } if (beforeColon) { String repStr = replaceParameterWithString(str, '?', "null"); retSql.append(repStr); continue; } else { retSql.append(str); continue; } } return retSql.toString(); } } void checkClosed() throws SQLServerException { // Check the connection first so that Statement methods // throw a "Connection closed" exception if the reason // that the statement was closed is because the connection // was closed. connection.checkClosed(); if (bIsClosed) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_statementIsClosed"), null, false); } } /* ---------------- JDBC API methods ------------------ */ @Override public final int getMaxFieldSize() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getMaxFieldSize"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getMaxFieldSize", maxFieldSize); return maxFieldSize; } @Override public final void setMaxFieldSize(int max) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "setMaxFieldSize", max); checkClosed(); if (max < 0) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength")); Object[] msgArgs = {max}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, true); } maxFieldSize = max; loggerExternal.exiting(getClassNameLogging(), "setMaxFieldSize"); } @Override public final int getMaxRows() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getMaxRows"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getMaxRows", maxRows); return maxRows; } @Override public final long getLargeMaxRows() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getLargeMaxRows"); // SQL Server only supports integer limits for setting max rows. // So, getLargeMaxRows() and getMaxRows() will return the same value. loggerExternal.exiting(getClassNameLogging(), "getLargeMaxRows", (long) maxRows); return (long) getMaxRows(); } @Override public final void setMaxRows(int max) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setMaxRows", max); checkClosed(); if (max < 0) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidRowcount")); Object[] msgArgs = {max}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, true); } // DYNAMIC scrollable server cursors have no notion of a keyset. That is, the number of rows in // the ResultSet can change dynamically as the app scrolls around. So there is no way to support // maxRows with DYNAMIC scrollable cursors. if (SQLServerResultSet.TYPE_SS_SCROLL_DYNAMIC != resultSetType) maxRows = max; loggerExternal.exiting(getClassNameLogging(), "setMaxRows"); } @Override public final void setLargeMaxRows(long max) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setLargeMaxRows", max); // SQL server only supports integer limits for setting max rows. // If <max> is bigger than integer limits then throw an exception, otherwise call setMaxRows(int) if (max > Integer.MAX_VALUE) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidMaxRows")); Object[] msgArgs = {max}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, true); } setMaxRows((int) max); loggerExternal.exiting(getClassNameLogging(), "setLargeMaxRows"); } @Override public final void setEscapeProcessing(boolean enable) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setEscapeProcessing", enable); checkClosed(); escapeProcessing = enable; loggerExternal.exiting(getClassNameLogging(), "setEscapeProcessing"); } @Override public final int getQueryTimeout() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getQueryTimeout"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getQueryTimeout", queryTimeout); return queryTimeout; } @Override public final void setQueryTimeout(int seconds) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "setQueryTimeout", seconds); checkClosed(); if (seconds < 0) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidQueryTimeOutValue")); Object[] msgArgs = {seconds}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, true); } queryTimeout = seconds; loggerExternal.exiting(getClassNameLogging(), "setQueryTimeout"); } @Override public final int getCancelQueryTimeout() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getCancelQueryTimeout"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getCancelQueryTimeout", cancelQueryTimeoutSeconds); return cancelQueryTimeoutSeconds; } @Override public final void setCancelQueryTimeout(int seconds) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "setCancelQueryTimeout", seconds); checkClosed(); if (seconds < 0) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidCancelQueryTimeout")); Object[] msgArgs = {seconds}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, true); } cancelQueryTimeoutSeconds = seconds; loggerExternal.exiting(getClassNameLogging(), "setCancelQueryTimeout"); } @Override public final void cancel() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "cancel"); checkClosed(); // Cancel the currently executing statement. if (null != currentCommand) currentCommand.interrupt(SQLServerException.getErrString("R_queryCancelled")); loggerExternal.exiting(getClassNameLogging(), "cancel"); } Vector<SQLWarning> sqlWarnings; // the SQL warnings chain
Flag to indicate that it is an internal query to retrieve encryption metadata.
/** Flag to indicate that it is an internal query to retrieve encryption metadata. */
boolean isInternalEncryptionQuery; @Override public final SQLWarning getWarnings() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getWarnings"); checkClosed(); if (sqlWarnings == null) return null; SQLWarning warn = sqlWarnings.elementAt(0); loggerExternal.exiting(getClassNameLogging(), "getWarnings", warn); return warn; } @Override public final void clearWarnings() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "clearWarnings"); checkClosed(); sqlWarnings = null; loggerExternal.exiting(getClassNameLogging(), "clearWarnings"); } @Override public final void setCursorName(String name) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "setCursorName", name); checkClosed(); cursorName = name; loggerExternal.exiting(getClassNameLogging(), "setCursorName"); } final String getCursorName() { return cursorName; } @Override public final java.sql.ResultSet getResultSet() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getResultSet"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getResultSet", resultSet); return resultSet; } @Override public final int getUpdateCount() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getUpdateCount"); checkClosed(); // this shouldn't happen, caller probably meant to call getLargeUpdateCount if (updateCount < Integer.MIN_VALUE || updateCount > Integer.MAX_VALUE) SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_updateCountOutofRange"), null, true); loggerExternal.exiting(getClassNameLogging(), "getUpdateCount", updateCount); return (int) updateCount; } @Override public final long getLargeUpdateCount() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getUpdateCount"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getUpdateCount", updateCount); return updateCount; } final void ensureExecuteResultsReader(TDSReader tdsReader) { this.tdsReader = tdsReader; } final void processExecuteResults() throws SQLServerException { if (wasExecuted()) { processBatch(); TDSParser.parse(resultsReader(), "batch completion"); ensureExecuteResultsReader(null); } } void processBatch() throws SQLServerException { processResults(); } final void processResults() throws SQLServerException { SQLServerException interruptException = null; while (moreResults) { // Get the next result try { getNextResult(true); } catch (SQLServerException e) { // If an exception is thrown while processing the results // then decide what to do with it: if (moreResults) { // Silently discard database errors and continue processing the remaining results if (SQLServerException.DRIVER_ERROR_FROM_DATABASE == e.getDriverErrorCode()) { if (stmtlogger.isLoggable(java.util.logging.Level.FINEST)) { stmtlogger.finest( this + " ignoring database error: " + e.getErrorCode() + " " + e.getMessage()); } continue; } // If statement execution was canceled then continue processing the // remaining results before throwing the "statement canceled" exception. if (e.getSQLState() != null && e.getSQLState().equals(SQLState.STATEMENT_CANCELED.getSQLStateCode())) { interruptException = e; continue; } } // Propagate up more serious exceptions to the caller moreResults = false; throw e; } } clearLastResult(); if (null != interruptException) throw interruptException; }
Returns more results in the TDS stream.
Returns:true if the next result is a ResultSet object; false if it is an integer (indicating that it is an update count or there are no more results).
/** * Returns more results in the TDS stream. * * @return true if the next result is a ResultSet object; false if it is an integer (indicating that it is an update * count or there are no more results). */
@Override public final boolean getMoreResults() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getMoreResults"); checkClosed(); // Get the next result, whatever it is (ResultSet or update count). // Don't just return the value from the getNextResult() call, however. // The getMoreResults method has a subtle spec for its return value (see above). getNextResult(true); loggerExternal.exiting(getClassNameLogging(), "getMoreResults", null != resultSet); return null != resultSet; }
Clears the most recent result (ResultSet or update count) from this statement. This method is used to clean up before moving to the next result and after processing the last result. Note that errors closing the ResultSet (if the last result is a ResultSet) are caught, logged, and ignored. The reason for this is that this method is only for cleanup for when the app fails to do the cleanup itself (for example by leaving a ResultSet open when closing the statement). If the app wants to be able to handle errors from closing the current result set, then it should close that ResultSet itself before closing the statement, moving to the next result, or whatever. This is the recommended practice with JDBC.
/** * Clears the most recent result (ResultSet or update count) from this statement. * * This method is used to clean up before moving to the next result and after processing the last result. * * Note that errors closing the ResultSet (if the last result is a ResultSet) are caught, logged, and ignored. The * reason for this is that this method is only for cleanup for when the app fails to do the cleanup itself (for * example by leaving a ResultSet open when closing the statement). If the app wants to be able to handle errors * from closing the current result set, then it should close that ResultSet itself before closing the statement, * moving to the next result, or whatever. This is the recommended practice with JDBC. */
final void clearLastResult() { // Clear any update count result updateCount = -1; // Clear any ResultSet result if (null != resultSet) { // Try closing the ResultSet. If closing fails, log the error and ignore it. // Then just drop our reference to the ResultSet object -- we're done with // it anyhow. try { resultSet.close(); } catch (SQLServerException e) { stmtlogger.finest(this + " clearing last result; ignored error closing ResultSet: " + e.getErrorCode() + " " + e.getMessage()); } finally { resultSet = null; } } }
Returns the next result in the TDS response token stream, which may be a result set, update count or exception.
Params:
  • clearFlag – Boolean Flag if set to true, clears the last stored results in ResultSet. If set to false, does not clear ResultSet and continues processing TDS Stream for more results.
Returns:true if another result (ResultSet or update count) was available; false if there were no more results.
/** * Returns the next result in the TDS response token stream, which may be a result set, update count or exception. * * @param clearFlag * Boolean Flag if set to true, clears the last stored results in ResultSet. If set to false, does not clear * ResultSet and continues processing TDS Stream for more results. * * @return true if another result (ResultSet or update count) was available; false if there were no more results. */
final boolean getNextResult(boolean clearFlag) throws SQLServerException {
TDS response token stream handler used to locate the next result in the TDS response token stream.
/** * TDS response token stream handler used to locate the next result in the TDS response token stream. */
final class NextResult extends TDSTokenHandler { private StreamDone stmtDoneToken = null; final boolean isUpdateCount() { return null != stmtDoneToken; } final long getUpdateCount() { return stmtDoneToken.getUpdateCount(); } private boolean isResultSet = false; final boolean isResultSet() { return isResultSet; } private StreamRetStatus procedureRetStatToken = null; NextResult() { super("getNextResult"); } boolean onColMetaData(TDSReader tdsReader) throws SQLServerException { /* * If we have an update count from a previous command that we haven't acknowledged because we didn't * know at the time whether it was the undesired result from a trigger, and if we did not encounter an * ERROR token before hitting this COLMETADATA token, with any intervening DONE token (does not indicate * an error result), then go ahead. */ if (null == stmtDoneToken && null == getDatabaseError()) { // If both conditions are true, column metadata indicates the start of a ResultSet isResultSet = true; } return false; } boolean onDone(TDSReader tdsReader) throws SQLServerException { // Consume the done token and decide what to do with it... // Handling DONE/DONEPROC/DONEINPROC tokens is a little tricky... StreamDone doneToken = new StreamDone(); doneToken.setFromTDS(tdsReader); // If the done token has the attention ack bit set, then record // it as the attention ack DONE token. We may or may not throw // an statement canceled/timed out exception later based on // whether we've seen this ack. if (doneToken.isAttnAck()) return false; // The meaning of other done tokens depends on several things ... // // A done token from a DML or DDL command normally contains an update // count that is usually returned as the next result. But processing // doesn't stop until we reach the end of the result, which may include // other tokens. And there are exceptions to that rule ... if (doneToken.cmdIsDMLOrDDL()) { // If update counts are suppressed (i.e. SET NOCOUNT ON was used) then // the DML/DDL update count is not valid, and this result should be skipped // unless it's for a batch where it's ok to report a "done without count" // status (Statement.SUCCESS_NO_INFO) if (-1 == doneToken.getUpdateCount() && EXECUTE_BATCH != executeMethod) return true; if (-1 != doneToken.getUpdateCount() && EXECUTE_QUERY == executeMethod) return true; // Otherwise, the update count is valid. Now determine whether we should // return it as a result... stmtDoneToken = doneToken; // Always return update counts from directly executed SQL statements. // This encompases both batch execution in normal SQLServerStatement objects // and compound statement execution, which are indistinguishable from one // another as they are both just concatenated statements. if (TDS.TDS_DONEINPROC != doneToken.getTokenType()) return false; if (EXECUTE_BATCH != executeMethod) { // Always return update counts from statements executed in stored procedure calls. if (null != procedureName) return false; // Always return all update counts from statements executed through Statement.execute() if (EXECUTE == executeMethod) return false; // Statement.executeUpdate() may or may not return this update count depending on the // setting of the lastUpdateCount connection property: // // If lastUpdateCount=true (the default), then skip this update count and continue. // Otherwise (lastUpdateCount=false), return this update count. if (!connection.useLastUpdateCount()) return false; // An update count from a statement that appears to have been executed in a stored // procedure call, when no stored procedure was called, is assumed to be the result of // a trigger firing. Skip it for now until/unless it is shown to be the last update // count of a statement's execution. } } // A done token without an update count usually just indicates the execution // of a SQL statement that isn't an INSERT, UPDATE, DELETE or DDL, and it should // be ignored. However, some of these done tokens have special meaning that we // need to consider... else { // The final done token in the response always marks the end of the result, // even if there is no update count. if (doneToken.isFinal()) { moreResults = false; return false; } if (EXECUTE_BATCH == executeMethod) { // The done token that marks the end of a batch always marks the end // of the result, even if there is no update count. if (TDS.TDS_DONEINPROC != doneToken.getTokenType() || doneToken.wasRPCInBatch()) { moreResults = false; return false; } } } // If the current command (whatever it was) produced an error then stop parsing and propagate it up. // In this case, the command is likely to be a RAISERROR, but it could be anything. if (doneToken.isError()) return false; // In all other cases, keep parsing return true; } boolean onRetStatus(TDSReader tdsReader) throws SQLServerException { // If this return status token marks the start of statement execution OUT parameters, // then consume those OUT parameters, setting server cursor ID, row count, and/or // the prepared statement handle from them. if (consumeExecOutParam(tdsReader)) { moreResults = false; } // If this RETSTATUS token is not the one that begins the statement execution OUT // parameters, then it must be one from a stored procedure that was called by // the query itself. In that case, it should be ignored as it does not contribute // to any result. It may mark the start of application OUT parameters, however, // so remember that we've seen it so that we don't ignore the RETVALUE tokens that // may follow. else { procedureRetStatToken = new StreamRetStatus(); procedureRetStatToken.setFromTDS(tdsReader); } return true; } boolean onRetValue(TDSReader tdsReader) throws SQLServerException { // We are only interested in return values that are statement OUT parameters, // in which case we need to stop parsing and let CallableStatement take over. // A RETVALUE token appearing in the execution results, but before any RETSTATUS // token, is a TEXTPTR return value that should be ignored. if (moreResults && null == procedureRetStatToken) { Parameter p = new Parameter( Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection)); p.skipRetValStatus(tdsReader); p.skipValue(tdsReader, true); return true; } return false; } boolean onInfo(TDSReader tdsReader) throws SQLServerException { StreamInfo infoToken = new StreamInfo(); infoToken.setFromTDS(tdsReader); // Under some circumstances the server cannot produce the cursored result set // that we requested, but produces a client-side (default) result set instead. // When this happens, there are no cursor ID or rowcount OUT parameters -- the // result set rows are returned directly as if this were a client-cursored // result set. // // ErrorNumber: 16954 // ErrorSeverity: EX_USER // ErrorFormat: Executing SQL directly; no cursor. // ErrorCause: Server cursor is not supported on the specified SQL, falling back to default result set // ErrorCorrectiveAction: None required // if (16954 == infoToken.msg.getErrorNumber()) executedSqlDirectly = true; SQLWarning warning = new SQLWarning( infoToken.msg.getErrorMessage(), SQLServerException.generateStateCode(connection, infoToken.msg.getErrorNumber(), infoToken.msg.getErrorState()), infoToken.msg.getErrorNumber()); if (sqlWarnings == null) { sqlWarnings = new Vector<>(); } else { int n = sqlWarnings.size(); SQLWarning w = sqlWarnings.elementAt(n - 1); w.setNextWarning(warning); } sqlWarnings.add(warning); return true; } } // If the statement has no results yet, then return if (!wasExecuted()) { moreResults = false; return false; } // Clear out previous results only when clearFlag = true if (clearFlag) { clearLastResult(); } // If there are no more results, then we're done. All we had to do was to close out the previous results. if (!moreResults) { return false; } // Figure out the next result. NextResult nextResult = new NextResult(); // Signal to not read all token other than TDS_MSG if reading only warnings TDSParser.parse(resultsReader(), nextResult, !clearFlag); // Check for errors first. if (null != nextResult.getDatabaseError()) { SQLServerException.makeFromDatabaseError(connection, null, nextResult.getDatabaseError().getErrorMessage(), nextResult.getDatabaseError(), false); } // If we didn't clear current ResultSet, we wanted to read only warnings. Return back from here. if (!clearFlag) return false; // Not an error. Is it a result set? else if (nextResult.isResultSet()) { resultSet = new SQLServerResultSet(this); return true; } // Nope. Not an error or a result set. Maybe a result from a T-SQL statement? // That is, one of the following: // - a positive count of the number of rows affected (from INSERT, UPDATE, or DELETE), // - a zero indicating no rows affected, or the statement was DDL, or // - a -1 indicating the statement succeeded, but there is no update count // information available (translates to Statement.SUCCESS_NO_INFO in batch // update count arrays). else if (nextResult.isUpdateCount()) { updateCount = nextResult.getUpdateCount(); return true; } // None of the above. Last chance here... Going into the parser above, we know // moreResults was initially true. If we come out with moreResults false, then // we hit a DONE token (either DONE (FINAL) or DONE (RPC in batch)) that indicates // that the batch succeeded overall, but that there is no information on individual // statements' update counts. This is similar to the last case above, except that // there is no update count. That is: we have a successful result (return true), // but we have no other information about it (updateCount = -1). updateCount = -1; if (!moreResults) { return true; } // Only way to get here (moreResults is still true, but no apparent results of any kind) // is if the TDSParser didn't actually parse _anything_. That is, we are at EOF in the // response. In that case, there truly are no more results. We're done. moreResults = false; return false; }
Consumes the OUT parameter for the statement object itself. Normal Statement objects consume the server cursor OUT params when present. PreparedStatement and CallableStatement objects override this method to consume the prepared statement handle as well.
/** * Consumes the OUT parameter for the statement object itself. * * Normal Statement objects consume the server cursor OUT params when present. PreparedStatement and * CallableStatement objects override this method to consume the prepared statement handle as well. */
boolean consumeExecOutParam(TDSReader tdsReader) throws SQLServerException { if (expectCursorOutParams) { TDSParser.parse(tdsReader, new StmtExecOutParamHandler()); return true; } return false; } // --------------------------JDBC 2.0----------------------------- @Override public final void setFetchDirection(int nDir) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setFetchDirection", nDir); checkClosed(); if ((ResultSet.FETCH_FORWARD != nDir && ResultSet.FETCH_REVERSE != nDir && ResultSet.FETCH_UNKNOWN != nDir) || (ResultSet.FETCH_FORWARD != nDir && (SQLServerResultSet.TYPE_SS_DIRECT_FORWARD_ONLY == resultSetType || SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY == resultSetType))) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidFetchDirection")); Object[] msgArgs = {nDir}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false); } nFetchDirection = nDir; loggerExternal.exiting(getClassNameLogging(), "setFetchDirection"); } @Override public final int getFetchDirection() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getFetchDirection"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getFetchDirection", nFetchDirection); return nFetchDirection; } @Override public final void setFetchSize(int rows) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setFetchSize", rows); checkClosed(); if (rows < 0) SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_invalidFetchSize"), null, false); nFetchSize = (0 == rows) ? defaultFetchSize : rows; loggerExternal.exiting(getClassNameLogging(), "setFetchSize"); } @Override public final int getFetchSize() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getFetchSize"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getFetchSize", nFetchSize); return nFetchSize; } @Override public final int getResultSetConcurrency() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getResultSetConcurrency"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getResultSetConcurrency", resultSetConcurrency); return resultSetConcurrency; } @Override public final int getResultSetType() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getResultSetType"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "getResultSetType", appResultSetType); return appResultSetType; } @Override public void addBatch(String sql) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "addBatch", sql); checkClosed(); // Apps can use JDBC call syntax to call unparameterized stored procedures // through regular Statement objects. We need to ensure that any such JDBC // call syntax is rewritten here as SQL exec syntax. sql = ensureSQLSyntax(sql); batchStatementBuffer.add(sql); loggerExternal.exiting(getClassNameLogging(), "addBatch"); } @Override public void clearBatch() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "clearBatch"); checkClosed(); batchStatementBuffer.clear(); loggerExternal.exiting(getClassNameLogging(), "clearBatch"); }
Sends a batch of statements to the database.
/** * Sends a batch of statements to the database. */
@Override public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQLTimeoutException { loggerExternal.entering(getClassNameLogging(), "executeBatch"); if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } checkClosed(); discardLastExecutionResults(); try { // Initialize all the update counts to EXECUTE_FAILED. This is so that if the server // returns fewer update counts than we expected (for example, due to closing the connection // on a severe error), we will return errors for the unexecuted batches. final int batchSize = batchStatementBuffer.size(); int[] updateCounts = new int[batchSize]; for (int batchNum = 0; batchNum < batchSize; batchNum++) updateCounts[batchNum] = Statement.EXECUTE_FAILED; // Last exception thrown. If database errors are returned, then executeBatch throws a // BatchUpdateException with this exception and the update counts, including errors. SQLServerException lastError = null; for (int batchNum = 0; batchNum < batchSize; batchNum++) { // NOTE: // When making changes to anything below, consider whether similar changes need // to be made to PreparedStatement batch execution. try { if (0 == batchNum) { // First time through, execute the entire set of batches and return the first result executeStatement(new StmtBatchExecCmd(this)); } else { // Subsequent times through, just get the result from the next batch. // If there are not enough results (update counts) to satisfy the number of batches, // then bail, leaving EXECUTE_FAILED in the remaining slots of the update count array. startResults(); if (!getNextResult(true)) break; } if (null != resultSet) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_resultsetGeneratedForUpdate"), null, true); } else { updateCounts[batchNum] = (-1 != (int) updateCount) ? (int) updateCount : Statement.SUCCESS_NO_INFO; } } catch (SQLServerException e) { // If the failure was severe enough to close the connection or roll back a // manual transaction, then propagate the error up as a SQLServerException // now, rather than continue with the batch. if (connection.isSessionUnAvailable() || connection.rolledBackTransaction()) throw e; // Otherwise, the connection is OK and the transaction is still intact, // so just record the failure for the particular batch item. lastError = e; } } // If we had any errors then throw a BatchUpdateException with the partial results. if (null != lastError) { throw new BatchUpdateException(lastError.getMessage(), lastError.getSQLState(), lastError.getErrorCode(), updateCounts); } loggerExternal.exiting(getClassNameLogging(), "executeBatch", updateCounts); return updateCounts; } finally { // Regardless what happens, always clear out the batch after execution. // Note: Don't use the clearBatch API as it checks that the statement is // not closed, which it might be in the event of a severe error. batchStatementBuffer.clear(); } } // executeBatch @Override public long[] executeLargeBatch() throws SQLServerException, BatchUpdateException, SQLTimeoutException { loggerExternal.entering(getClassNameLogging(), "executeLargeBatch"); if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } checkClosed(); discardLastExecutionResults(); try { // Initialize all the update counts to EXECUTE_FAILED. This is so that if the server // returns fewer update counts than we expected (for example, due to closing the connection // on a severe error), we will return errors for the unexecuted batches. final int batchSize = batchStatementBuffer.size(); long[] updateCounts = new long[batchSize]; for (int batchNum = 0; batchNum < batchSize; batchNum++) updateCounts[batchNum] = Statement.EXECUTE_FAILED; // Last exception thrown. If database errors are returned, then executeBatch throws a // BatchUpdateException with this exception and the update counts, including errors. SQLServerException lastError = null; for (int batchNum = 0; batchNum < batchSize; batchNum++) { // NOTE: // When making changes to anything below, consider whether similar changes need // to be made to PreparedStatement batch execution. try { if (0 == batchNum) { // First time through, execute the entire set of batches and return the first result executeStatement(new StmtBatchExecCmd(this)); } else { // Subsequent times through, just get the result from the next batch. // If there are not enough results (update counts) to satisfy the number of batches, // then bail, leaving EXECUTE_FAILED in the remaining slots of the update count array. startResults(); if (!getNextResult(true)) break; } if (null != resultSet) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_resultsetGeneratedForUpdate"), null, true); } else { updateCounts[batchNum] = (-1 != updateCount) ? updateCount : Statement.SUCCESS_NO_INFO; } } catch (SQLServerException e) { // If the failure was severe enough to close the connection or roll back a // manual transaction, then propagate the error up as a SQLServerException // now, rather than continue with the batch. if (connection.isSessionUnAvailable() || connection.rolledBackTransaction()) throw e; // Otherwise, the connection is OK and the transaction is still intact, // so just record the failure for the particular batch item. lastError = e; } } // If we had any errors then throw a BatchUpdateException with the partial results. if (null != lastError) { DriverJDBCVersion.throwBatchUpdateException(lastError, updateCounts); } loggerExternal.exiting(getClassNameLogging(), "executeLargeBatch", updateCounts); return updateCounts; } finally { // Regardless what happens, always clear out the batch after execution. // Note: Don't use the clearBatch API as it checks that the statement is // not closed, which it might be in the event of a severe error. batchStatementBuffer.clear(); } } // executeLargeBatch
Returns the statement's connection.
Throws:
  • SQLServerException – when an error occurs
Returns:the connection
/** * Returns the statement's connection. * * @throws SQLServerException * when an error occurs * @return the connection */
@Override public final java.sql.Connection getConnection() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getConnection"); if (bIsClosed) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_statementIsClosed"), null, false); } java.sql.Connection con = connection.getConnection(); loggerExternal.exiting(getClassNameLogging(), "getConnection", con); return con; } /* ----------------- Server side cursor support -------------------------- */ final int getResultSetScrollOpt() { int scrollOpt = (null == inOutParam) ? 0 : TDS.SCROLLOPT_PARAMETERIZED_STMT; switch (resultSetType) { case SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY: return scrollOpt | ((ResultSet.CONCUR_READ_ONLY == resultSetConcurrency) ? TDS.SCROLLOPT_FAST_FORWARD : TDS.SCROLLOPT_FORWARD_ONLY); case SQLServerResultSet.TYPE_SS_SCROLL_DYNAMIC: return scrollOpt | TDS.SCROLLOPT_DYNAMIC; case SQLServerResultSet.TYPE_SS_SCROLL_KEYSET: return scrollOpt | TDS.SCROLLOPT_KEYSET; case SQLServerResultSet.TYPE_SS_SCROLL_STATIC: return scrollOpt | TDS.SCROLLOPT_STATIC; // Other (invalid) values were caught by the constructor. default: return 0; } } final int getResultSetCCOpt() { switch (resultSetConcurrency) { case ResultSet.CONCUR_READ_ONLY: return TDS.CCOPT_READ_ONLY | TDS.CCOPT_ALLOW_DIRECT; case SQLServerResultSet.CONCUR_SS_OPTIMISTIC_CC: // aka case ResultSet.CONCUR_UPDATABLE: return TDS.CCOPT_OPTIMISTIC_CC | TDS.CCOPT_UPDT_IN_PLACE | TDS.CCOPT_ALLOW_DIRECT; case SQLServerResultSet.CONCUR_SS_SCROLL_LOCKS: return TDS.CCOPT_SCROLL_LOCKS | TDS.CCOPT_UPDT_IN_PLACE | TDS.CCOPT_ALLOW_DIRECT; case SQLServerResultSet.CONCUR_SS_OPTIMISTIC_CCVAL: return TDS.CCOPT_OPTIMISTIC_CCVAL | TDS.CCOPT_UPDT_IN_PLACE | TDS.CCOPT_ALLOW_DIRECT; // Other (invalid) values were caught by the constructor. default: return 0; } } private void doExecuteCursored(StmtExecCmd execCmd, String sql) throws SQLServerException { if (stmtlogger.isLoggable(java.util.logging.Level.FINER)) { stmtlogger.finer(toString() + " Execute for cursor open" + " SQL:" + sql + " Scrollability:" + getResultSetScrollOpt() + " Concurrency:" + getResultSetCCOpt()); } executedSqlDirectly = false; expectCursorOutParams = true; TDSWriter tdsWriter = execCmd.startRequest(TDS.PKT_RPC); tdsWriter.writeShort((short) 0xFFFF); // procedure name length -> use ProcIDs tdsWriter.writeShort(TDS.PROCID_SP_CURSOROPEN); tdsWriter.writeByte((byte) 0); // RPC procedure option 1 tdsWriter.writeByte((byte) 0); // RPC procedure option 2 tdsWriter.sendEnclavePackage(sql, execCmd.enclaveCEKs); // <cursor> OUT tdsWriter.writeRPCInt(null, 0, true); // <stmt> IN tdsWriter.writeRPCStringUnicode(sql); // <scrollopt> IN tdsWriter.writeRPCInt(null, getResultSetScrollOpt(), false); // <ccopt> IN tdsWriter.writeRPCInt(null, getResultSetCCOpt(), false); // <rowcount> OUT tdsWriter.writeRPCInt(null, 0, true); ensureExecuteResultsReader(execCmd.startResponse(isResponseBufferingAdaptive)); startResults(); getNextResult(true); } /* JDBC 3.0 */ @Override public final int getResultSetHoldability() throws SQLException { loggerExternal.entering(getClassNameLogging(), "getResultSetHoldability"); checkClosed(); int holdability = connection.getHoldability(); // For SQL Server must be the same as the connection loggerExternal.exiting(getClassNameLogging(), "getResultSetHoldability", holdability); return holdability; } @Override public final boolean execute(java.lang.String sql, int autoGeneratedKeys) throws SQLServerException, SQLTimeoutException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) { loggerExternal.entering(getClassNameLogging(), "execute", new Object[] {sql, autoGeneratedKeys}); if (Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } } checkClosed(); if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS && autoGeneratedKeys != Statement.NO_GENERATED_KEYS) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidAutoGeneratedKeys")); Object[] msgArgs = {autoGeneratedKeys}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false); } executeStatement(new StmtExecCmd(this, sql, EXECUTE, autoGeneratedKeys)); loggerExternal.exiting(getClassNameLogging(), "execute", null != resultSet); return null != resultSet; } @Override public final boolean execute(java.lang.String sql, int[] columnIndexes) throws SQLServerException, SQLTimeoutException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "execute", new Object[] {sql, columnIndexes}); checkClosed(); if (columnIndexes == null || columnIndexes.length != 1) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_invalidColumnArrayLength"), null, false); } boolean fSuccess = execute(sql, Statement.RETURN_GENERATED_KEYS); loggerExternal.exiting(getClassNameLogging(), "execute", fSuccess); return fSuccess; } @Override public final boolean execute(java.lang.String sql, java.lang.String[] columnNames) throws SQLServerException, SQLTimeoutException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "execute", new Object[] {sql, columnNames}); checkClosed(); if (columnNames == null || columnNames.length != 1) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_invalidColumnArrayLength"), null, false); } boolean fSuccess = execute(sql, Statement.RETURN_GENERATED_KEYS); loggerExternal.exiting(getClassNameLogging(), "execute", fSuccess); return fSuccess; } @Override public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLServerException, SQLTimeoutException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) { loggerExternal.entering(getClassNameLogging(), "executeUpdate", new Object[] {sql, autoGeneratedKeys}); if (Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } } checkClosed(); if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS && autoGeneratedKeys != Statement.NO_GENERATED_KEYS) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidAutoGeneratedKeys")); Object[] msgArgs = {autoGeneratedKeys}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false); } executeStatement(new StmtExecCmd(this, sql, EXECUTE_UPDATE, autoGeneratedKeys)); // this shouldn't happen, caller probably meant to call executeLargeUpdate if (updateCount < Integer.MIN_VALUE || updateCount > Integer.MAX_VALUE) SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_updateCountOutofRange"), null, true); loggerExternal.exiting(getClassNameLogging(), "executeUpdate", updateCount); return (int) updateCount; } @Override public final long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLServerException, SQLTimeoutException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) { loggerExternal.entering(getClassNameLogging(), "executeLargeUpdate", new Object[] {sql, autoGeneratedKeys}); if (Util.isActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); } } checkClosed(); if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS && autoGeneratedKeys != Statement.NO_GENERATED_KEYS) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidAutoGeneratedKeys")); Object[] msgArgs = {autoGeneratedKeys}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false); } executeStatement(new StmtExecCmd(this, sql, EXECUTE_UPDATE, autoGeneratedKeys)); loggerExternal.exiting(getClassNameLogging(), "executeLargeUpdate", updateCount); return updateCount; } @Override public final int executeUpdate(java.lang.String sql, int[] columnIndexes) throws SQLServerException, SQLTimeoutException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "executeUpdate", new Object[] {sql, columnIndexes}); checkClosed(); if (columnIndexes == null || columnIndexes.length != 1) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_invalidColumnArrayLength"), null, false); } int count = executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); loggerExternal.exiting(getClassNameLogging(), "executeUpdate", count); return count; } @Override public final long executeLargeUpdate(java.lang.String sql, int[] columnIndexes) throws SQLServerException, SQLTimeoutException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "executeLargeUpdate", new Object[] {sql, columnIndexes}); checkClosed(); if (columnIndexes == null || columnIndexes.length != 1) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_invalidColumnArrayLength"), null, false); } long count = executeLargeUpdate(sql, Statement.RETURN_GENERATED_KEYS); loggerExternal.exiting(getClassNameLogging(), "executeLargeUpdate", count); return count; } @Override public final int executeUpdate(java.lang.String sql, String[] columnNames) throws SQLServerException, SQLTimeoutException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "executeUpdate", new Object[] {sql, columnNames}); checkClosed(); if (columnNames == null || columnNames.length != 1) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_invalidColumnArrayLength"), null, false); } int count = executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); loggerExternal.exiting(getClassNameLogging(), "executeUpdate", count); return count; } @Override public final long executeLargeUpdate(java.lang.String sql, String[] columnNames) throws SQLServerException, SQLTimeoutException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "executeLargeUpdate", new Object[] {sql, columnNames}); checkClosed(); if (columnNames == null || columnNames.length != 1) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_invalidColumnArrayLength"), null, false); } long count = executeLargeUpdate(sql, Statement.RETURN_GENERATED_KEYS); loggerExternal.exiting(getClassNameLogging(), "executeLargeUpdate", count); return count; } @Override public final ResultSet getGeneratedKeys() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getGeneratedKeys"); checkClosed(); if (null == autoGeneratedKeys) { long orgUpd = updateCount; // Generated keys are returned in a ResultSet result right after the update count. // Try to get that ResultSet. If there are no more results after the update count, // or if the next result isn't a ResultSet, then something is wrong. if (!getNextResult(true) || null == resultSet) { SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_statementMustBeExecuted"), null, false); } autoGeneratedKeys = resultSet; updateCount = orgUpd; } loggerExternal.exiting(getClassNameLogging(), "getGeneratedKeys", autoGeneratedKeys); return autoGeneratedKeys; } @Override public final boolean getMoreResults(int mode) throws SQLException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "getMoreResults", mode); checkClosed(); if (KEEP_CURRENT_RESULT == mode) { SQLServerException.throwNotSupportedException(connection, this); } if (CLOSE_CURRENT_RESULT != mode && CLOSE_ALL_RESULTS != mode) SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_modeSuppliedNotValid"), null, true); ResultSet rsPrevious = resultSet; boolean fResults = getMoreResults(); if (rsPrevious != null) { try { rsPrevious.close(); } catch (SQLException e) { throw new SQLServerException(e.getMessage(), null, 0, e); } } loggerExternal.exiting(getClassNameLogging(), "getMoreResults", fResults); return fResults; } @Override public boolean isClosed() throws SQLException { loggerExternal.entering(getClassNameLogging(), "isClosed"); boolean result = bIsClosed || connection.isSessionUnAvailable(); loggerExternal.exiting(getClassNameLogging(), "isClosed", result); return result; } @Override public boolean isCloseOnCompletion() throws SQLException { loggerExternal.entering(getClassNameLogging(), "isCloseOnCompletion"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "isCloseOnCompletion", isCloseOnCompletion); return isCloseOnCompletion; } @Override public boolean isPoolable() throws SQLException { loggerExternal.entering(getClassNameLogging(), "isPoolable"); checkClosed(); loggerExternal.exiting(getClassNameLogging(), "isPoolable", stmtPoolable); return stmtPoolable; } @Override public void setPoolable(boolean poolable) throws SQLException { loggerExternal.entering(getClassNameLogging(), "setPoolable", poolable); checkClosed(); stmtPoolable = poolable; loggerExternal.exiting(getClassNameLogging(), "setPoolable"); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { loggerExternal.entering(getClassNameLogging(), "isWrapperFor"); boolean f = iface.isInstance(this); loggerExternal.exiting(getClassNameLogging(), "isWrapperFor", f); return f; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { loggerExternal.entering(getClassNameLogging(), "unwrap"); T t; try { t = iface.cast(this); } catch (ClassCastException e) { throw new SQLServerException(e.getMessage(), e); } loggerExternal.exiting(getClassNameLogging(), "unwrap", t); return t; } // responseBuffering controls the driver's buffering of responses from SQL Server. // Possible values are: // // "full" - Fully buffer the response at execution time. // Advantages: // 100% back compat with v1.1 driver // Maximizes concurrency on the server // Disadvantages: // Consumes more client-side memory // Client scalability limits with large responses // More execute latency // // "adaptive" - Data Pipe adaptive buffering // Advantages: // Buffers only when necessary, only as much as necessary // Enables handling very large responses, values // Disadvantages // Reduced concurrency on the server @Override public final void setResponseBuffering(String value) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "setResponseBuffering", value); checkClosed(); if ("full".equalsIgnoreCase(value)) { isResponseBufferingAdaptive = false; wasResponseBufferingSet = true; } else if ("adaptive".equalsIgnoreCase(value)) { isResponseBufferingAdaptive = true; wasResponseBufferingSet = true; } else { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidresponseBuffering")); Object[] msgArgs = {value}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false); } loggerExternal.exiting(getClassNameLogging(), "setResponseBuffering"); } @Override public final String getResponseBuffering() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getResponseBuffering"); checkClosed(); String responseBuff; if (wasResponseBufferingSet) { if (isResponseBufferingAdaptive) responseBuff = "adaptive"; else responseBuff = "full"; } else { responseBuff = connection.getResponseBuffering(); } loggerExternal.exiting(getClassNameLogging(), "getResponseBuffering", responseBuff); return responseBuff; } }
Provides a helper class that does some basic parsing work for SQL statements that are stored procedure calls. - Determines whether the SQL uses JDBC call syntax ("{[? =] call procedure_name...}") or T-SQL EXECUTE syntax ("EXEC [@p0 =] procedure_name..."). If JDBC call syntax is present, it gets rewritten as T-SQL EXECUTE syntax. - Determines whether the caller expects a return value from the stored procedure (see the optional return value syntax in [] above). - Extracts the stored procedure name from the call. SQL statements that are not stored procedure calls are passed through unchanged.
/** * Provides a helper class that does some basic parsing work for SQL statements that are stored procedure calls. * * - Determines whether the SQL uses JDBC call syntax ("{[? =] call procedure_name...}") or T-SQL EXECUTE syntax ("EXEC * [@p0 =] procedure_name..."). If JDBC call syntax is present, it gets rewritten as T-SQL EXECUTE syntax. * * - Determines whether the caller expects a return value from the stored procedure (see the optional return value * syntax in [] above). * * - Extracts the stored procedure name from the call. * * SQL statements that are not stored procedure calls are passed through unchanged. */
final class JDBCSyntaxTranslator { private String procedureName = null; String getProcedureName() { return procedureName; } private boolean hasReturnValueSyntax = false; boolean hasReturnValueSyntax() { return hasReturnValueSyntax; } /* * SQL Identifier regex Loosely follows the spec'd SQL identifier syntax: - anything between escape characters * (square brackets or double quotes), including escaped escape characters, OR - any contiguous string of * non-whitespace characters. - including multipart identifiers */ private final static String sqlIdentifierPart = "(?:(?:\\[(?:[^\\]]|(?:\\]\\]))+?\\])|(?:\"(?:[^\"]|(?:\"\"))+?\")|(?:\\S+?))"; private final static String sqlIdentifierWithoutGroups = "(" + sqlIdentifierPart + "(?:\\." + sqlIdentifierPart + "){0,3}?)"; private final static String sqlIdentifierWithGroups = "(" + sqlIdentifierPart + ")" + "(?:\\." + "(" + sqlIdentifierPart + "))?"; // This is used in three part name matching. static String getSQLIdentifierWithGroups() { return sqlIdentifierWithGroups; } /* * JDBC call syntax regex From the JDBC spec: {call procedure_name} {call procedure_name(?, ?, ...)} {? = call * procedure_name[(?, ?, ...)]} allowing for arbitrary amounts of whitespace in the obvious places. */ private final static Pattern jdbcCallSyntax = Pattern .compile("(?s)\\s*?\\{\\s*?(\\?\\s*?=)?\\s*?[cC][aA][lL][lL]\\s+?" + sqlIdentifierWithoutGroups + "(?:\\s*?\\((.*)\\))?\\s*\\}.*+"); /* * T-SQL EXECUTE syntax regex EXEC | EXECUTE [@return_result =] procedure_name [parameters] allowing for arbitrary * amounts of whitespace in the obvious places. */ private final static Pattern sqlExecSyntax = Pattern.compile("\\s*?[eE][xX][eE][cC](?:[uU][tT][eE])??\\s+?(" + sqlIdentifierWithoutGroups + "\\s*?=\\s+?)??" + sqlIdentifierWithoutGroups + "(?:$|(?:\\s+?.*+))"); /* * JDBC limit escape syntax From the JDBC spec: {LIMIT <rows> [OFFSET <row_offset>]} The driver currently does not * support the OFFSET part. It will throw an exception if used. */ enum State { START, END, SUBQUERY, // also handles anything inside any (), e.g. scalar functions SELECT, OPENQUERY, OPENROWSET, LIMIT, OFFSET, QUOTE, PROCESS } // This pattern matches the LIMIT syntax with an OFFSET clause. The driver does not support OFFSET expression in the // LIMIT clause. // It will throw an exception if OFFSET is present in the LIMIT escape syntax. private final static Pattern limitSyntaxWithOffset = Pattern .compile("\\{\\s*[lL][iI][mM][iI][tT]\\s+(.*)\\s+[oO][fF][fF][sS][eE][tT]\\s+(.*)\\}"); // This pattern is used to determine if the query has LIMIT escape syntax. If so, then the query is further // processed to translate the syntax. private final static Pattern limitSyntaxGeneric = Pattern .compile("\\{\\s*[lL][iI][mM][iI][tT]\\s+(.*)(\\s+[oO][fF][fF][sS][eE][tT](.*)\\}|\\s*\\})"); private final static Pattern selectPattern = Pattern.compile("([sS][eE][lL][eE][cC][tT])\\s+"); // OPENQUERY ( linked_server ,'query' ) private final static Pattern openQueryPattern = Pattern .compile("[oO][pP][eE][nN][qQ][uU][eE][rR][yY]\\s*\\(.*,\\s*'(.*)'\\s*\\)"); /* * OPENROWSET ( 'provider_name', { 'datasource' ; 'user_id' ; 'password' | 'provider_string' }, { [ catalog. ] [ * schema. ] object | 'query' } ) */ private final static Pattern openRowsetPattern = Pattern .compile("[oO][pP][eE][nN][rR][oO][wW][sS][eE][tT]\\s*\\(.*,.*,\\s*'(.*)'\\s*\\)"); /* * {limit 30} {limit ?} {limit (?)} */ private final static Pattern limitOnlyPattern = Pattern .compile("\\{\\s*[lL][iI][mM][iI][tT]\\s+(((\\(|\\s)*)(\\d*|\\?)((\\)|\\s)*))\\s*\\}");
Translates the LIMIT escape syntax, {LIMIT [OFFSET ]} SQL Server does not support LIMIT syntax, the LIMIT escape syntax is thus translated to use "TOP" syntax The OFFSET clause is not supported, and will throw an exception if used.
Params:
  • sql – the SQL query
  • indx – Position in the query from where to start translation
  • endChar – The character that marks the end of translation
Throws:
Returns:the number of characters that have been translated
/** * Translates the LIMIT escape syntax, {LIMIT <row> [OFFSET <offset>]} SQL Server does not support LIMIT syntax, the * LIMIT escape syntax is thus translated to use "TOP" syntax The OFFSET clause is not supported, and will throw an * exception if used. * * @param sql * the SQL query * * @param indx * Position in the query from where to start translation * * @param endChar * The character that marks the end of translation * * @throws SQLServerException * * @return the number of characters that have been translated * */
int translateLimit(StringBuffer sql, int indx, char endChar) throws SQLServerException { Matcher selectMatcher = selectPattern.matcher(sql); Matcher openQueryMatcher = openQueryPattern.matcher(sql); Matcher openRowsetMatcher = openRowsetPattern.matcher(sql); Matcher limitMatcher = limitOnlyPattern.matcher(sql); Matcher offsetMatcher = limitSyntaxWithOffset.matcher(sql); int startIndx = indx; Stack<Integer> topPosition = new Stack<>(); State nextState = State.START; while (indx < sql.length()) { char ch = sql.charAt(indx); switch (nextState) { case START: nextState = State.PROCESS; break; case PROCESS: // The search for endChar should come before the search for quote (') as openquery has quote(') as // the endChar if (endChar == ch) { nextState = State.END; } else if ('\'' == ch) { nextState = State.QUOTE; } else if ('(' == ch) { nextState = State.SUBQUERY; } else if (limitMatcher.find(indx) && indx == limitMatcher.start()) { nextState = State.LIMIT; } else if (offsetMatcher.find(indx) && indx == offsetMatcher.start()) { nextState = State.OFFSET; } else if (openQueryMatcher.find(indx) && indx == openQueryMatcher.start()) { nextState = State.OPENQUERY; } else if (openRowsetMatcher.find(indx) && indx == openRowsetMatcher.start()) { nextState = State.OPENROWSET; } else if (selectMatcher.find(indx) && indx == selectMatcher.start()) { nextState = State.SELECT; } else indx++; break; case OFFSET: // throw exception as OFFSET is not supported // SQLState is null as this error is generated in the driver throw new SQLServerException(SQLServerException.getErrString("R_limitOffsetNotSupported"), null, 0, // Use // 0 // instead // of // DriverError.NOT_SET // to // use // the // correct // constructor null); case LIMIT: // Check if the number of opening/closing parentheses surrounding the digits or "?" in LIMIT match // Count the number of opening parentheses. int openingParentheses = 0, closingParentheses = 0; int pos = -1; String openingStr = limitMatcher.group(2); String closingStr = limitMatcher.group(5); while (-1 != (pos = openingStr.indexOf('(', pos + 1))) { openingParentheses++; } pos = -1; while (-1 != (pos = closingStr.indexOf(')', pos + 1))) { closingParentheses++; } if (openingParentheses != closingParentheses) { // SQLState is null as this error is generated in the driver throw new SQLServerException(SQLServerException.getErrString("R_limitEscapeSyntaxError"), null, 0, // Use 0 instead of DriverError.NOT_SET to use the correct constructor null); } /* * 'topPosition' is a stack that keeps track of the positions where the next "TOP" should be * inserted. The SELECT expressions are matched with the closest LIMIT expressions unless in a * subquery with explicit parentheses, that's why it needs to be a stack. To translate, we add the * clause <TOP rows> after SELECT and delete the clause {LIMIT rows}. */ if (!topPosition.empty()) { Integer top = topPosition.pop(); String rows = limitMatcher.group(1); // Delete the LIMIT clause. sql.delete(limitMatcher.start() - 1, limitMatcher.end()); // Add the TOP clause. if ('?' == rows.charAt(0)) { // For parameterized queries the '?' needs to wrapped in parentheses. sql.insert(top, " TOP (" + rows + ")"); // add the letters/spaces inserted with TOP, add the digits from LIMIT, subtract one because // the // current letter at the index is deleted. indx += 7 + rows.length() - 1; } else { sql.insert(top, " TOP " + rows); indx += 5 + rows.length() - 1; } } else { // Could not match LIMIT with a SELECT, should never occur. // But if it does, just ignore // Matcher.end() returns offset after the last character of matched string indx = limitMatcher.end() - 1; } nextState = State.PROCESS; break; case SELECT: indx = selectMatcher.end(1); topPosition.push(indx); nextState = State.PROCESS; break; case QUOTE: // Consume the current character indx++; if (sql.length() > indx && '\'' == sql.charAt(indx)) { // Consume the quote. // If this is part of an escaped quote, stay in QUOTE state, else go to PROCESS // To escape a quote SQL Server requires two quotes indx++; if (sql.length() > indx && '\'' == sql.charAt(indx)) { nextState = State.QUOTE; } else { nextState = State.PROCESS; } } else { nextState = State.QUOTE; } break; case SUBQUERY: // Consume the opening bracket. indx++; // Consume the subquery. indx += translateLimit(sql, indx, ')'); nextState = State.PROCESS; break; case OPENQUERY: // skip the characters until query start. indx = openQueryMatcher.start(1); indx += translateLimit(sql, indx, '\''); nextState = State.PROCESS; break; case OPENROWSET: // skip the characters until query start. indx = openRowsetMatcher.start(1); indx += translateLimit(sql, indx, '\''); nextState = State.PROCESS; break; case END: // Consume the endChar character found indx++; return indx - startIndx; default: // This should never occur. // throw break; } } // end of while return indx - startIndx; } String translate(String sql) throws SQLServerException { Matcher matcher; matcher = jdbcCallSyntax.matcher(sql); if (matcher.matches()) { // Figure out the procedure name and whether there is a return value and then // rewrite the JDBC call syntax as T-SQL EXEC syntax. hasReturnValueSyntax = (null != matcher.group(1)); procedureName = matcher.group(2); String args = matcher.group(3); sql = "EXEC " + (hasReturnValueSyntax ? "? = " : "") + procedureName + ((null != args) ? (" " + args) : ""); } else { matcher = sqlExecSyntax.matcher(sql); if (matcher.matches()) { // Figure out the procedure name and whether there is a return value, // but do not rewrite the statement as it is already in T-SQL EXEC syntax. hasReturnValueSyntax = (null != matcher.group(1)); procedureName = matcher.group(3); } } // Search for LIMIT escape syntax. Do further processing if present. matcher = limitSyntaxGeneric.matcher(sql); if (matcher.find()) { StringBuffer sqlbuf = new StringBuffer(sql); translateLimit(sqlbuf, 0, '\0'); return sqlbuf.toString(); } // 'sql' is modified if CALL or LIMIT escape sequence is present, Otherwise pass it straight through. return sql; } }