com.microsoft.sqlserver.jdbc.SQLServerStatement Maven / Gradle / Ivy
Show all versions of mssql-jdbc Show documentation
/*
* 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 java.sql.BatchUpdateException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.ListIterator;
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;
/**
* SQLServerStatment provides the basic implementation of JDBC statement functionality. 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.
*/
public class SQLServerStatement implements ISQLServerStatement {
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) */
String procedureName;
/** 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 */
boolean stmtPoolable;
/** 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.
*/
Parameter[] inOutParam; // Parameters for prepared stmts and stored procedures
/**
* The statements connection.
*/
final SQLServerConnection connection;
/**
* The user's specifed query timeout (in seconds).
*/
int queryTimeout;
/**
* 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.
*/
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;
/**
* ExecuteProperties 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).
*/
final void executeStatement(TDSCommand newStmtCmd) throws SQLServerException {
// 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);
}
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.
*/
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.
*/
boolean moreResults = false;
/**
* The statement's current result set.
*/
SQLServerResultSet resultSet;
/**
* The number of opened result sets in the statement.
*/
int resultSetCount = 0;
/**
* Increment opened result set counter
*/
synchronized void incrResultSetCount() {
resultSetCount++;
}
/**
* 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.
*/
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.
*/
long updateCount = -1;
/**
* The status of escape processing.
*/
boolean escapeProcessing;
/** 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 */
int maxFieldSize = 0; // default: 0 --> no limit
/**
* 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
*/
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
*/
int resultSetType;
final int getSQLResultSetType() {
return resultSetType;
}
final int getCursorType() {
return getResultSetScrollOpt() & ~TDS.SCROLLOPT_PARAMETERIZED_STMT;
}
/**
* Indicates 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.
*/
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.
*/
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.
*/
String cursorName;
/**
* 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
*/
int nFetchDirection;
/**
* True is the statment is closed
*/
boolean bIsClosed;
/**
* True if the user requested to driver to generate insert keys
*/
boolean bRequestedGeneratedKeys;
/**
* 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 */
private final ArrayList batchStatementBuffer = new ArrayList();
/** logging init at the construction */
static final private java.util.logging.Logger stmtlogger = java.util.logging.Logger
.getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerStatement");
/** The statement's id for logging info */
public String toString() {
return traceID;
}
// Internal function used in tracing
String getClassNameInternal() {
return "SQLServerStatement";
}
/** Generate the statement's logging ID */
private static final AtomicInteger lastStatementID = new AtomicInteger(0);
private static int nextStatementID() {
return lastStatementID.incrementAndGet();
}
/**
* The regular statement constructor
*
* @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;
final int nTypes = 5;
// 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 || !selectMethod.equals("cursor")) ? 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();
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() + ")");
}
}
// 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;
}
/**
* Standard handler for unsupported data types.
*/
/* L0 */ final void NotImplemented() throws SQLServerException {
SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_notSupported"), null, false);
}
/**
* Close 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;
}
public void close() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "close");
if (!bIsClosed)
closeInternal();
loggerExternal.exiting(getClassNameLogging(), "close");
}
public void closeOnCompletion() throws SQLException {
loggerExternal.entering(getClassNameLogging(), "closeOnCompletion");
checkClosed();
// enable closeOnCompletion feature
isCloseOnCompletion = true;
loggerExternal.exiting(getClassNameLogging(), "closeOnCompletion");
}
/**
* Execute a result set query
*
* @param sql
* the SQL query
* @exception SQLServerException
* The SQL was invalid.
* @return a JDBC result set.
*/
public java.sql.ResultSet executeQuery(String sql) throws SQLServerException {
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 {
checkClosed();
executeStatement(new StmtExecCmd(this, sql, EXECUTE_QUERY_INTERNAL, NO_GENERATED_KEYS));
return resultSet;
}
/**
* Execute a JDBC update
*
* @param sql
* the SQL query
* @exception SQLServerException
* The SQL was invalid.
* @return The number of rows updated.
*/
public int executeUpdate(String sql) throws SQLServerException {
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", new Long(updateCount));
return (int) updateCount;
}
/**
* Execute a JDBC update
*
* @param sql
* the SQL query
* @exception SQLServerException
* The SQL was invalid.
* @return The number of rows updated.
*/
public long executeLargeUpdate(String sql) throws SQLServerException {
DriverJDBCVersion.checkSupportsJDBC42();
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", new Long(updateCount));
return updateCount;
}
/**
* Execute an update or query.
*
* @param sql
* The update or query.
* @exception SQLServerException
* The SQL statement was not valid.
* @return True if a result set was generated.
*/
public boolean execute(String sql) throws SQLServerException {
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", Boolean.valueOf(null != resultSet));
return null != resultSet;
}
private final class StmtExecCmd extends TDSCommand {
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);
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) {
JDBCSyntaxTranslator translator = new JDBCSyntaxTranslator();
String execSyntax = translator.translate(sql);
procedureName = translator.getProcedureName();
return execSyntax;
}
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 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.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();
}
// 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 {
final SQLServerStatement stmt;
StmtBatchExecCmd(SQLServerStatement stmt) {
super(stmt.toString() + " executeBatch", stmt.queryTimeout);
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);
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
ListIterator batchIter = batchStatementBuffer.listIterator();
tdsWriter.writeString(batchIter.next());
while (batchIter.hasNext()) {
tdsWriter.writeString(" ; ");
tdsWriter.writeString(batchIter.next());
}
// Start the response
ensureExecuteResultsReader(execCmd.startResponse(isResponseBufferingAdaptive));
startResults();
getNextResult();
// If execution produced a result set, then throw an exception
if (null != resultSet) {
SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_resultsetGeneratedForUpdate"), null, false);
}
}
/**
* Reset 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();
}
/**
* Determine if the SQL is a SELECT.
*
* @param sql
* The statment SQL.
* @return True is the statement is a select.
*/
/* L0 */ 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();
char c = temp.charAt(0);
if (c != 's' && c != 'S')
return false;
return temp.substring(0, 6).equalsIgnoreCase("select");
}
/**
* Replace 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
*/
/* L0 */ 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;
}
/**
* Set a JDBC parameter to null. (Used only when processing LOB column sources.)
*
* @param sql
* the parameter syntax
* @return the result
*/
/* L0 */ static String replaceMarkerWithNull(String sql) {
if (!sql.contains("'")) {
String retStr = replaceParameterWithString(sql, '?', "null");
return retStr;
}
else {
StringTokenizer st = new StringTokenizer(sql, "'", true);
boolean beforeColon = true;
String retSql = "";
while (st.hasMoreTokens()) {
String str = st.nextToken();
if (str.equals("'")) {
retSql += "'";
beforeColon = !beforeColon;
continue;
}
if (beforeColon) {
String repStr = replaceParameterWithString(str, '?', "null");
retSql += repStr;
continue;
}
else {
retSql += str;
continue;
}
}
return retSql;
}
}
/* L0 */ 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 ------------------ */
/* L0 */ public final int getMaxFieldSize() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getMaxFieldSize");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "getMaxFieldSize", new Integer(maxFieldSize));
return maxFieldSize;
}
/* L0 */ public final void setMaxFieldSize(int max) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "setMaxFieldSize", new Integer(max));
checkClosed();
if (max < 0) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength"));
Object[] msgArgs = {new Integer(max)};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, true);
}
maxFieldSize = max;
loggerExternal.exiting(getClassNameLogging(), "setMaxFieldSize");
}
/* L0 */ public final int getMaxRows() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getMaxRows");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "getMaxRows", new Integer(maxRows));
return maxRows;
}
public final long getLargeMaxRows() throws SQLServerException {
DriverJDBCVersion.checkSupportsJDBC42();
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", new Long(maxRows));
return (long) getMaxRows();
}
/* L0 */ public final void setMaxRows(int max) throws SQLServerException {
if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "setMaxRows", new Integer(max));
checkClosed();
if (max < 0) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidRowcount"));
Object[] msgArgs = {new Integer(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");
}
public final void setLargeMaxRows(long max) throws SQLServerException {
DriverJDBCVersion.checkSupportsJDBC42();
if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "setLargeMaxRows", new Long(max));
// SQL server only supports integer limits for setting max rows.
// If is bigger than integer limits then throw an exception, otherwise call setMaxRows(int)
if (max > Integer.MAX_VALUE) {
throw new UnsupportedOperationException(SQLServerException.getErrString("R_invalidMaxRows"));
}
setMaxRows((int) max);
loggerExternal.exiting(getClassNameLogging(), "setLargeMaxRows");
}
/* L0 */ public final void setEscapeProcessing(boolean enable) throws SQLServerException {
if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "setEscapeProcessing", Boolean.valueOf(enable));
checkClosed();
escapeProcessing = enable;
loggerExternal.exiting(getClassNameLogging(), "setEscapeProcessing");
}
/* L0 */ public final int getQueryTimeout() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getQueryTimeout");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "getQueryTimeout", new Integer(queryTimeout));
return queryTimeout;
}
/* L0 */ public final void setQueryTimeout(int seconds) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "setQueryTimeout", new Integer(seconds));
checkClosed();
if (seconds < 0) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidQueryTimeOutValue"));
Object[] msgArgs = {new Integer(seconds)};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, true);
}
queryTimeout = seconds;
loggerExternal.exiting(getClassNameLogging(), "setQueryTimeout");
}
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 sqlWarnings; // the SQL warnings chain
/* L0 */ 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;
}
/* L0 */ public final void clearWarnings() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "clearWarnings");
checkClosed();
sqlWarnings = null;
loggerExternal.exiting(getClassNameLogging(), "clearWarnings");
}
/* L0 */ 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;
}
public final java.sql.ResultSet getResultSet() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getResultSet");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "getResultSet", resultSet);
return resultSet;
}
/* L0 */ 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", new Long(updateCount));
return (int) updateCount;
}
public final long getLargeUpdateCount() throws SQLServerException {
DriverJDBCVersion.checkSupportsJDBC42();
loggerExternal.entering(getClassNameLogging(), "getUpdateCount");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "getUpdateCount", new Long(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();
}
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().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;
}
/**
* Check for 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).
*/
/* L0 */ 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();
loggerExternal.exiting(getClassNameLogging(), "getMoreResults", Boolean.valueOf(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.
*/
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;
}
}
}
/**
* Get the next result in the TDS response token stream, which may be a result set, update count or exception.
*
* @return true if another result (ResultSet or update count) was available; false if there were no more results.
*/
final boolean getNextResult() throws SQLServerException {
/**
* 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, we now know that it wasn't,
// so return it.
if (null != stmtDoneToken)
return false;
// If we encountered an ERROR token before hitting this COLMETADATA token,
// without any intervening DONE token (indicating an error result), then
// act as if that had been the case and drop out to propagate the error
// up as an SQLException.
if (null != getDatabaseError())
return false;
// Otherwise, 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.getMessage(),
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
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();
TDSParser.parse(resultsReader(), nextResult);
// Check for errors first.
if (null != nextResult.getDatabaseError()) {
SQLServerException.makeFromDatabaseError(connection, null, nextResult.getDatabaseError().getMessage(), nextResult.getDatabaseError(),
false);
}
// Not an error. Is it a result set?
else if (nextResult.isResultSet()) {
if (Util.use42Wrapper()) {
resultSet = new SQLServerResultSet42(this);
}
else {
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;
}
/**
* Consume 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-----------------------------
public final void setFetchDirection(int nDir) throws SQLServerException {
if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "setFetchDirection", new Integer(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 = {new Integer(nDir)};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false);
}
nFetchDirection = nDir;
loggerExternal.exiting(getClassNameLogging(), "setFetchDirection");
}
public final int getFetchDirection() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getFetchDirection");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "getFetchDirection", new Integer(nFetchDirection));
return nFetchDirection;
}
/* L0 */ public final void setFetchSize(int rows) throws SQLServerException {
if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "setFetchSize", new Integer(rows));
checkClosed();
if (rows < 0)
SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_invalidFetchSize"), null, false);
nFetchSize = (0 == rows) ? defaultFetchSize : rows;
loggerExternal.exiting(getClassNameLogging(), "setFetchSize");
}
/* L0 */ public final int getFetchSize() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getFetchSize");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "getFetchSize", new Integer(nFetchSize));
return nFetchSize;
}
/* L0 */ public final int getResultSetConcurrency() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getResultSetConcurrency");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "getResultSetConcurrency", new Integer(resultSetConcurrency));
return resultSetConcurrency;
}
/* L0 */ public final int getResultSetType() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getResultSetType");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "getResultSetType", new Integer(appResultSetType));
return appResultSetType;
}
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");
}
public void clearBatch() throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "clearBatch");
checkClosed();
batchStatementBuffer.clear();
loggerExternal.exiting(getClassNameLogging(), "clearBatch");
}
/**
* Send a batch of statements to the database.
*/
public int[] executeBatch() throws SQLServerException, BatchUpdateException {
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())
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
public long[] executeLargeBatch() throws SQLServerException, BatchUpdateException {
DriverJDBCVersion.checkSupportsJDBC42();
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())
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
/**
* Return the statement's connection
*
* @throws SQLServerException
* when an error occurs
* @return the connection
*/
/* L0 */ 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 -------------------------- */
/**
* Open a server side cursor.
*
* @param sql
* The SQL query.
* @exception SQLServerException
* The SQL query was invalid.
*/
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.
}
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.
}
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
// OUT
tdsWriter.writeRPCInt(null, new Integer(0), true);
// IN
tdsWriter.writeRPCStringUnicode(sql);
// IN
tdsWriter.writeRPCInt(null, new Integer(getResultSetScrollOpt()), false);
// IN
tdsWriter.writeRPCInt(null, new Integer(getResultSetCCOpt()), false);
// OUT
tdsWriter.writeRPCInt(null, new Integer(0), true);
ensureExecuteResultsReader(execCmd.startResponse(isResponseBufferingAdaptive));
startResults();
getNextResult();
}
/* JDBC 3.0 */
/* L3 */ 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", new Integer(holdability));
return holdability;
}
public final boolean execute(java.lang.String sql,
int autoGeneratedKeys) throws SQLServerException {
if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) {
loggerExternal.entering(getClassNameLogging(), "execute", new Object[] {sql, new Integer(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 = {new Integer(autoGeneratedKeys)};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false);
}
executeStatement(new StmtExecCmd(this, sql, EXECUTE, autoGeneratedKeys));
loggerExternal.exiting(getClassNameLogging(), "execute", Boolean.valueOf(null != resultSet));
return null != resultSet;
}
public final boolean execute(java.lang.String sql,
int[] columnIndexes) throws SQLServerException {
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", Boolean.valueOf(fSuccess));
return fSuccess;
}
public final boolean execute(java.lang.String sql,
java.lang.String[] columnNames) throws SQLServerException {
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", Boolean.valueOf(fSuccess));
return fSuccess;
}
public final int executeUpdate(String sql,
int autoGeneratedKeys) throws SQLServerException {
if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) {
loggerExternal.entering(getClassNameLogging(), "executeUpdate", new Object[] {sql, new Integer(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 = {new Integer(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", new Long(updateCount));
return (int) updateCount;
}
public final long executeLargeUpdate(String sql,
int autoGeneratedKeys) throws SQLServerException {
DriverJDBCVersion.checkSupportsJDBC42();
if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) {
loggerExternal.entering(getClassNameLogging(), "executeLargeUpdate", new Object[] {sql, new Integer(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 = {new Integer(autoGeneratedKeys)};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false);
}
executeStatement(new StmtExecCmd(this, sql, EXECUTE_UPDATE, autoGeneratedKeys));
loggerExternal.exiting(getClassNameLogging(), "executeLargeUpdate", new Long(updateCount));
return updateCount;
}
public final int executeUpdate(java.lang.String sql,
int[] columnIndexes) throws SQLServerException {
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", new Integer(count));
return count;
}
public final long executeLargeUpdate(java.lang.String sql,
int[] columnIndexes) throws SQLServerException {
DriverJDBCVersion.checkSupportsJDBC42();
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", new Long(count));
return count;
}
public final int executeUpdate(java.lang.String sql,
String[] columnNames) throws SQLServerException {
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", new Integer(count));
return count;
}
public final long executeLargeUpdate(java.lang.String sql,
String[] columnNames) throws SQLServerException {
DriverJDBCVersion.checkSupportsJDBC42();
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", new Long(count));
return count;
}
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() || null == resultSet) {
SQLServerException.makeFromDriverError(connection, this, SQLServerException.getErrString("R_statementMustBeExecuted"), null, false);
}
autoGeneratedKeys = resultSet;
updateCount = orgUpd;
}
loggerExternal.exiting(getClassNameLogging(), "getGeneratedKeys", autoGeneratedKeys);
return autoGeneratedKeys;
}
/* L3 */ public final boolean getMoreResults(int mode) throws SQLServerException {
if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "getMoreResults", new Integer(mode));
checkClosed();
if (KEEP_CURRENT_RESULT == mode)
NotImplemented();
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", Boolean.valueOf(fResults));
return fResults;
}
public boolean isClosed() throws SQLException {
loggerExternal.entering(getClassNameLogging(), "isClosed");
boolean result = bIsClosed || connection.isSessionUnAvailable();
loggerExternal.exiting(getClassNameLogging(), "isClosed", result);
return result;
}
public boolean isCloseOnCompletion() throws SQLException {
loggerExternal.entering(getClassNameLogging(), "isCloseOnCompletion");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "isCloseOnCompletion", isCloseOnCompletion);
return isCloseOnCompletion;
}
public boolean isPoolable() throws SQLException {
loggerExternal.entering(getClassNameLogging(), "isPoolable");
checkClosed();
loggerExternal.exiting(getClassNameLogging(), "isPoolable", stmtPoolable);
return stmtPoolable;
}
public void setPoolable(boolean poolable) throws SQLException {
loggerExternal.entering(getClassNameLogging(), "setPoolable", poolable);
checkClosed();
stmtPoolable = poolable;
loggerExternal.exiting(getClassNameLogging(), "setPoolable");
}
public boolean isWrapperFor(Class> iface) throws SQLException {
loggerExternal.entering(getClassNameLogging(), "isWrapperFor");
boolean f = iface.isInstance(this);
loggerExternal.exiting(getClassNameLogging(), "isWrapperFor", Boolean.valueOf(f));
return f;
}
public T unwrap(Class 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
public final void setResponseBuffering(String value) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "setResponseBuffering", value);
checkClosed();
if (value.equalsIgnoreCase("full")) {
isResponseBufferingAdaptive = false;
wasResponseBufferingSet = true;
}
else if (value.equalsIgnoreCase("adaptive")) {
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");
}
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;
}
}
/**
* 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 [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*\\}");
/*
* This function 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.
*
* @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 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
throw new SQLServerException(SQLServerException.getErrString("R_limitOffsetNotSupported"), null, // SQLState is null as this error
// is generated in the driver
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) {
throw new SQLServerException(SQLServerException.getErrString("R_limitEscapeSyntaxError"), null, // SQLState is null as this
// error is generated in the
// driver
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 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);
}
}
// LIMIT escape is introduced in JDBC 4.1. Make sure versions lower than 4.1 do not have this feature.
if (((4 == DriverJDBCVersion.major) && (1 <= DriverJDBCVersion.minor)) || (4 < DriverJDBCVersion.major)) {
// 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;
}
}