internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, out bool dataReady)
{
Debug.Assert((SniContext.Undefined != stateObj.SniContext) && // SniContext must not be Undefined
((stateObj._attentionSent) || ((SniContext.Snix_Execute != stateObj.SniContext) && (SniContext.Snix_SendRows != stateObj.SniContext))), // SniContext should not be Execute or SendRows unless attention was sent (and, therefore, we are looking for an ACK)
String.Format("Unexpected SniContext on call to TryRun; SniContext={0}", stateObj.SniContext));
if (TdsParserState.Broken == State || TdsParserState.Closed == State)
{
dataReady = true;
return true; // Just in case this is called in a loop, expecting data to be returned.
}
dataReady = false;
do
{
// If there is data ready, but we didn't exit the loop, then something is wrong
Debug.Assert(!dataReady, "dataReady not expected - did we forget to skip the row?");
if (stateObj._internalTimeout)
{
runBehavior = RunBehavior.Attention;
}
if (TdsParserState.Broken == State || TdsParserState.Closed == State)
break; // jump out of the loop if the state is already broken or closed.
if (!stateObj._accumulateInfoEvents && (stateObj._pendingInfoEvents != null))
{
if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior))
{
SqlConnection connection = null;
if (_connHandler != null)
connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref
// We are omitting checks for error.Class in the code below (see processing of INFO) since we know (and assert) that error class
// error.Class < TdsEnums.MIN_ERROR_CLASS for info message.
// Also we know that TdsEnums.MIN_ERROR_CLASS<TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS
if ((connection != null) && connection.FireInfoMessageEventOnUserErrors)
{
foreach (SqlError error in stateObj._pendingInfoEvents)
FireInfoMessageEvent(connection, stateObj, error);
}
else
foreach (SqlError error in stateObj._pendingInfoEvents)
stateObj.AddWarning(error);
}
stateObj._pendingInfoEvents = null;
}
byte token;
if (!stateObj.TryReadByte(out token))
{
return false;
}
if (!IsValidTdsToken(token))
{
Debug.Assert(false, String.Format((IFormatProvider)null, "unexpected token; token = {0,-2:X2}", token));
_state = TdsParserState.Broken;
_connHandler.BreakConnection();
throw SQL.ParsingError();
}
int tokenLength;
if (!TryGetTokenLength(token, stateObj, out tokenLength))
{
return false;
}
switch (token)
{
case TdsEnums.SQLERROR:
case TdsEnums.SQLINFO:
{
if (token == TdsEnums.SQLERROR)
{
stateObj._errorTokenReceived = true; // Keep track of the fact error token was received - for Done processing.
}
SqlError error;
if (!TryProcessError(token, stateObj, out error))
{
return false;
}
if (token == TdsEnums.SQLINFO && stateObj._accumulateInfoEvents)
{
Debug.Assert(error.Class < TdsEnums.MIN_ERROR_CLASS, "INFO with class > TdsEnums.MIN_ERROR_CLASS");
if (stateObj._pendingInfoEvents == null)
stateObj._pendingInfoEvents = new List<SqlError>();
stateObj._pendingInfoEvents.Add(error);
stateObj._syncOverAsync = true;
break;
}
if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior))
{
// If FireInfoMessageEventOnUserErrors is true, we have to fire event without waiting.
// Otherwise we can go ahead and add it to errors/warnings collection.
SqlConnection connection = null;
if (_connHandler != null)
connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref
if ((connection != null) &&
(connection.FireInfoMessageEventOnUserErrors == true) &&
(error.Class <= TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS))
{
// Fire SqlInfoMessage here
FireInfoMessageEvent(connection, stateObj, error);
}
else
{
// insert error/info into the appropriate exception - warning if info, exception if error
if (error.Class < TdsEnums.MIN_ERROR_CLASS)
{
stateObj.AddWarning(error);
}
else if (error.Class < TdsEnums.FATAL_ERROR_CLASS)
{
// Continue results processing for all non-fatal errors (<20)
stateObj.AddError(error);
// Add it to collection - but do NOT change run behavior UNLESS
// we are in an ExecuteReader call - at which time we will be throwing
// anyways so we need to consume all errors. This is not the case
// if we have already given out a reader. If we have already given out
// a reader we need to throw the error but not halt further processing. We used to
// halt processing.
if (null != dataStream)
{
if (!dataStream.IsInitialized)
{
runBehavior = RunBehavior.UntilDone;
}
}
}
else
{
stateObj.AddError(error);
// Else we have a fatal error and we need to change the behavior
// since we want the complete error information in the exception.
// Besides - no further results will be received.
runBehavior = RunBehavior.UntilDone;
}
}
}
else if (error.Class >= TdsEnums.FATAL_ERROR_CLASS)
{
stateObj.AddError(error);
}
break;
}
case TdsEnums.SQLCOLINFO:
{
if (null != dataStream)
{
_SqlMetaDataSet metaDataSet;
if (!TryProcessColInfo(dataStream.MetaData, dataStream, stateObj, out metaDataSet))
{
return false;
}
if (!dataStream.TrySetMetaData(metaDataSet, false))
{
return false;
}
dataStream.BrowseModeInfoConsumed = true;
}
else
{ // no dataStream
if (!stateObj.TrySkipBytes(tokenLength))
{
return false;
}
}
break;
}
case TdsEnums.SQLDONE:
case TdsEnums.SQLDONEPROC:
case TdsEnums.SQLDONEINPROC:
{
// RunBehavior can be modified
if (!TryProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj))
{
return false;
}
if ((token == TdsEnums.SQLDONEPROC) && (cmdHandler != null))
{
cmdHandler.OnDoneProc();
}
break;
}
case TdsEnums.SQLORDER:
{
// don't do anything with the order token so read off the pipe
if (!stateObj.TrySkipBytes(tokenLength))
{
return false;
}
break;
}
case TdsEnums.SQLALTMETADATA:
{
stateObj.CloneCleanupAltMetaDataSetArray();
if (stateObj._cleanupAltMetaDataSetArray == null)
{
// create object on demand (lazy creation)
stateObj._cleanupAltMetaDataSetArray = new _SqlMetaDataSetCollection();
}
_SqlMetaDataSet cleanupAltMetaDataSet;
if (!TryProcessAltMetaData(tokenLength, stateObj, out cleanupAltMetaDataSet))
{
return false;
}
stateObj._cleanupAltMetaDataSetArray.SetAltMetaData(cleanupAltMetaDataSet);
if (null != dataStream)
{
byte metadataConsumedByte;
if (!stateObj.TryPeekByte(out metadataConsumedByte))
{
return false;
}
if (!dataStream.TrySetAltMetaDataSet(cleanupAltMetaDataSet, (TdsEnums.SQLALTMETADATA != metadataConsumedByte)))
{
return false;
}
}
break;
}
case TdsEnums.SQLALTROW:
{
if (!stateObj.TryStartNewRow(isNullCompressed: false))
{ // altrows are not currently null compressed
return false;
}
// read will call run until dataReady. Must not read any data if returnimmetiately set
if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))
{
ushort altRowId;
if (!stateObj.TryReadUInt16(out altRowId))
{ // get altRowId
return false;
}
if (!TrySkipRow(stateObj._cleanupAltMetaDataSetArray.GetAltMetaData(altRowId), stateObj))
{ // skip altRow
return false;
}
}
else
{
dataReady = true;
}
break;
}
case TdsEnums.SQLENVCHANGE:
{
// ENVCHANGE must be processed synchronously (since it can modify the state of many objects)
stateObj._syncOverAsync = true;
SqlEnvChange[] env;
if (!TryProcessEnvChange(tokenLength, stateObj, out env))
{
return false;
}
for (int ii = 0; ii < env.Length; ii++)
{
if (env[ii] != null && !this.Connection.IgnoreEnvChange)
{
switch (env[ii].type)
{
case TdsEnums.ENV_BEGINTRAN:
// When we get notification from the server of a new
// transaction, we move any pending transaction over to
// the current transaction, then we store the token in it.
// if there isn't a pending transaction, then it's either
// a TSQL transaction or a distributed transaction.
Debug.Assert(null == _currentTransaction, "non-null current transaction with an ENV Change");
_currentTransaction = _pendingTransaction;
_pendingTransaction = null;
if (null != _currentTransaction)
{
_currentTransaction.TransactionId = env[ii].newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec.
}
else
{
TransactionType transactionType = TransactionType.LocalFromTSQL;
_currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env[ii].newLongValue);
}
if (null != _statistics && !_statisticsIsInTransaction)
{
_statistics.SafeIncrement(ref _statistics._transactions);
}
_statisticsIsInTransaction = true;
break;
case TdsEnums.ENV_COMMITTRAN:
// SQLHOT 483
// Must clear the retain id if the server-side transaction ends by anything other
// than rollback.
goto case TdsEnums.ENV_ROLLBACKTRAN;
case TdsEnums.ENV_ROLLBACKTRAN:
// When we get notification of a completed transaction
// we null out the current transaction.
if (null != _currentTransaction)
{
#if DEBUG
// Check null for case where Begin and Rollback obtained in the same message.
if (SqlInternalTransaction.NullTransactionId != _currentTransaction.TransactionId)
{
Debug.Assert(_currentTransaction.TransactionId != env[ii].newLongValue, "transaction id's are not equal!");
}
#endif
if (TdsEnums.ENV_COMMITTRAN == env[ii].type)
{
_currentTransaction.Completed(TransactionState.Committed);
}
else if (TdsEnums.ENV_ROLLBACKTRAN == env[ii].type)
{
// Hold onto transaction id if distributed tran is rolled back. This must
// be sent to the server on subsequent executions even though the transaction
// is considered to be rolled back.
_currentTransaction.Completed(TransactionState.Aborted);
}
else
{
_currentTransaction.Completed(TransactionState.Unknown);
}
_currentTransaction = null;
}
_statisticsIsInTransaction = false;
break;
case TdsEnums.ENV_ENLISTDTC:
case TdsEnums.ENV_DEFECTDTC:
case TdsEnums.ENV_TRANSACTIONENDED:
Debug.Assert(false, "Should have thrown if DTC token encountered");
break;
default:
_connHandler.OnEnvChange(env[ii]);
break;
}
}
}
break;
}
case TdsEnums.SQLLOGINACK:
{
SqlLoginAck ack;
if (!TryProcessLoginAck(stateObj, out ack))
{
return false;
}
_connHandler.OnLoginAck(ack);
break;
}
case TdsEnums.SQLFEATUREEXTACK:
{
if (!TryProcessFeatureExtAck(stateObj))
{
return false;
}
break;
}
case TdsEnums.SQLSESSIONSTATE:
{
if (!TryProcessSessionState(stateObj, tokenLength, _connHandler._currentSessionData))
{
return false;
}
break;
}
case TdsEnums.SQLCOLMETADATA:
{
if (tokenLength != TdsEnums.VARNULL)
{
_SqlMetaDataSet metadata;
if (!TryProcessMetaData(tokenLength, stateObj, out metadata))
{
return false;
}
stateObj._cleanupMetaData = metadata;
}
else
{
if (cmdHandler != null)
{
stateObj._cleanupMetaData = cmdHandler.MetaData;
}
}
if (null != dataStream)
{
byte peekedToken;
if (!stateObj.TryPeekByte(out peekedToken))
{ // temporarily cache next byte
return false;
}
if (!dataStream.TrySetMetaData(stateObj._cleanupMetaData, (TdsEnums.SQLTABNAME == peekedToken || TdsEnums.SQLCOLINFO == peekedToken)))
{
return false;
}
}
else if (null != bulkCopyHandler)
{
bulkCopyHandler.SetMetaData(stateObj._cleanupMetaData);
}
break;
}
case TdsEnums.SQLROW:
case TdsEnums.SQLNBCROW:
{
Debug.Assert(stateObj._cleanupMetaData != null, "Reading a row, but the metadata is null");
if (token == TdsEnums.SQLNBCROW)
{
if (!stateObj.TryStartNewRow(isNullCompressed: true, nullBitmapColumnsCount: stateObj._cleanupMetaData.Length))
{
return false;
}
}
else
{
if (!stateObj.TryStartNewRow(isNullCompressed: false))
{
return false;
}
}
if (null != bulkCopyHandler)
{
if (!TryProcessRow(stateObj._cleanupMetaData, bulkCopyHandler.CreateRowBuffer(), bulkCopyHandler.CreateIndexMap(), stateObj))
{
return false;
}
}
else if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))
{
if (!TrySkipRow(stateObj._cleanupMetaData, stateObj))
{ // skip rows
return false;
}
}
else
{
dataReady = true;
}
if (_statistics != null)
{
_statistics.WaitForDoneAfterRow = true;
}
break;
}
case TdsEnums.SQLRETURNSTATUS:
int status;
if (!stateObj.TryReadInt32(out status))
{
return false;
}
if (cmdHandler != null)
{
cmdHandler.OnReturnStatus(status);
}
break;
case TdsEnums.SQLRETURNVALUE:
{
SqlReturnValue returnValue;
if (!TryProcessReturnValue(tokenLength, stateObj, out returnValue))
{
return false;
}
if (cmdHandler != null)
{
cmdHandler.OnReturnValue(returnValue);
}
break;
}
case TdsEnums.SQLSSPI:
{
// token length is length of SSPI data - call ProcessSSPI with it
Debug.Assert(stateObj._syncOverAsync, "ProcessSSPI does not support retry, do not attempt asynchronously");
stateObj._syncOverAsync = true;
ProcessSSPI(tokenLength);
break;
}
case TdsEnums.SQLTABNAME:
{
{
if (!stateObj.TrySkipBytes(tokenLength))
{
return false;
}
}
break;
}
default:
Debug.Assert(false, "Unhandled token: " + token.ToString(CultureInfo.InvariantCulture));
break;
}
Debug.Assert(stateObj._pendingData || !dataReady, "dataReady is set, but there is no pending data");
}
// Loop while data pending & runbehavior not return immediately, OR
// if in attention case, loop while no more pending data & attention has not yet been
// received.
while ((stateObj._pendingData &&
(RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))) ||
(!stateObj._pendingData && stateObj._attentionSent && !stateObj._attentionReceived));
#if DEBUG
if ((stateObj._pendingData) && (!dataReady))
{
byte token;
if (!stateObj.TryPeekByte(out token))
{
return false;
}
Debug.Assert(IsValidTdsToken(token), string.Format("DataReady is false, but next token is not valid: {0,-2:X2}", token));
}
#endif
if (!stateObj._pendingData)
{
if (null != CurrentTransaction)
{
CurrentTransaction.Activate();
}
}
// if we received an attention (but this thread didn't send it) then
// we throw an Operation Cancelled error
if (stateObj._attentionReceived)
{
// Dev11 #344723: SqlClient stress hang System_Data!Tcp::ReadSync via a call to SqlDataReader::Close
// Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent
SpinWait.SpinUntil(() => !stateObj._attentionSending);
Debug.Assert(stateObj._attentionSent, "Attention ACK has been received without attention sent");
if (stateObj._attentionSent)
{
// Reset attention state.
stateObj._attentionSent = false;
stateObj._attentionReceived = false;
if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj._internalTimeout)
{
// Add attention error to collection - if not RunBehavior.Clean!
stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.OperationCancelled(), "", 0));
}
}
}
if (stateObj.HasErrorOrWarning)
{
ThrowExceptionAndWarning(stateObj);
}
return true;
}