// // Read in a SQLVariant // // SQLVariant looks like: // struct // { // BYTE TypeTag // BYTE cbPropBytes // BYTE[] Properties // BYTE[] DataVal // } internal bool TryReadSqlVariant(SqlBuffer value, int lenTotal, TdsParserStateObject stateObj) { // get the SQLVariant type byte type; if (!stateObj.TryReadByte(out type)) { return false; } ushort lenMax = 0; // maximum lenData of value inside variant // read cbPropBytes byte cbPropsActual; if (!stateObj.TryReadByte(out cbPropsActual)) { return false; } MetaType mt = MetaType.GetSqlDataType(type, 0 /*no user datatype*/, 0 /* no lenData, non-nullable type */); byte cbPropsExpected = mt.PropBytes; int lenConsumed = TdsEnums.SQLVARIANT_SIZE + cbPropsActual; // type, count of propBytes, and actual propBytes int lenData = lenTotal - lenConsumed; // length of actual data // read known properties and skip unknown properties Debug.Assert(cbPropsActual >= cbPropsExpected, "cbPropsActual is less that cbPropsExpected!"); // // now read the value // switch (type) { case TdsEnums.SQLBIT: case TdsEnums.SQLINT1: case TdsEnums.SQLINT2: case TdsEnums.SQLINT4: case TdsEnums.SQLINT8: case TdsEnums.SQLFLT4: case TdsEnums.SQLFLT8: case TdsEnums.SQLMONEY: case TdsEnums.SQLMONEY4: case TdsEnums.SQLDATETIME: case TdsEnums.SQLDATETIM4: case TdsEnums.SQLUNIQUEID: if (!TryReadSqlValueInternal(value, type, lenData, stateObj)) { return false; } break; case TdsEnums.SQLDECIMALN: case TdsEnums.SQLNUMERICN: { Debug.Assert(cbPropsExpected == 2, "SqlVariant: invalid PropBytes for decimal/numeric type!"); byte precision; if (!stateObj.TryReadByte(out precision)) { return false; } byte scale; if (!stateObj.TryReadByte(out scale)) { return false; } // skip over unknown properties if (cbPropsActual > cbPropsExpected) { if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) { return false; } } if (!TryReadSqlDecimal(value, TdsEnums.MAX_NUMERIC_LEN, precision, scale, stateObj)) { return false; } break; } case TdsEnums.SQLBIGBINARY: case TdsEnums.SQLBIGVARBINARY: //Debug.Assert(TdsEnums.VARNULL == lenData, "SqlVariant: data length for Binary indicates null?"); Debug.Assert(cbPropsExpected == 2, "SqlVariant: invalid PropBytes for binary type!"); if (!stateObj.TryReadUInt16(out lenMax)) { return false; } Debug.Assert(lenMax != TdsEnums.SQL_USHORTVARMAXLEN, "bigvarbinary(max) in a sqlvariant"); // skip over unknown properties if (cbPropsActual > cbPropsExpected) { if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) { return false; } } goto case TdsEnums.SQLBIT; case TdsEnums.SQLBIGCHAR: case TdsEnums.SQLBIGVARCHAR: case TdsEnums.SQLNCHAR: case TdsEnums.SQLNVARCHAR: { Debug.Assert(cbPropsExpected == 7, "SqlVariant: invalid PropBytes for character type!"); SqlCollation collation; if (!TryProcessCollation(stateObj, out collation)) { return false; } if (!stateObj.TryReadUInt16(out lenMax)) { return false; } Debug.Assert(lenMax != TdsEnums.SQL_USHORTVARMAXLEN, "bigvarchar(max) or nvarchar(max) in a sqlvariant"); // skip over unknown properties if (cbPropsActual > cbPropsExpected) { if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) { return false; } } Encoding encoding = Encoding.GetEncoding(GetCodePage(collation, stateObj)); if (!TryReadSqlStringValue(value, type, lenData, encoding, false, stateObj)) { return false; } break; } case TdsEnums.SQLDATE: if (!TryReadSqlDateTime(value, type, lenData, 0, stateObj)) { return false; } break; case TdsEnums.SQLTIME: case TdsEnums.SQLDATETIME2: case TdsEnums.SQLDATETIMEOFFSET: { Debug.Assert(cbPropsExpected == 1, "SqlVariant: invalid PropBytes for time/datetime2/datetimeoffset type!"); byte scale; if (!stateObj.TryReadByte(out scale)) { return false; } // skip over unknown properties if (cbPropsActual > cbPropsExpected) { if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) { return false; } } if (!TryReadSqlDateTime(value, type, lenData, scale, stateObj)) { return false; } break; } default: Debug.Assert(false, "Unknown tds type in SqlVariant!" + type.ToString(CultureInfo.InvariantCulture)); break; } // switch return true; }
internal bool TrySkipPlpValue(ulong cb, TdsParserStateObject stateObj, out ulong totalBytesSkipped) { // Read and skip cb bytes or until ReadPlpLength returns 0. int bytesSkipped; totalBytesSkipped = 0; if (stateObj._longlenleft == 0) { ulong ignored; if (!stateObj.TryReadPlpLength(false, out ignored)) { return false; } } while ((totalBytesSkipped < cb) && (stateObj._longlenleft > 0)) { if (stateObj._longlenleft > Int32.MaxValue) bytesSkipped = Int32.MaxValue; else bytesSkipped = (int)stateObj._longlenleft; bytesSkipped = ((cb - totalBytesSkipped) < (ulong)bytesSkipped) ? (int)(cb - totalBytesSkipped) : bytesSkipped; if (!stateObj.TrySkipBytes(bytesSkipped)) { return false; } stateObj._longlenleft -= (ulong)bytesSkipped; totalBytesSkipped += (ulong)bytesSkipped; if (stateObj._longlenleft == 0) { ulong ignored; if (!stateObj.TryReadPlpLength(false, out ignored)) { return false; } } } return true; }
private bool TryProcessColumnHeaderNoNBC(SqlMetaDataPriv col, TdsParserStateObject stateObj, out bool isNull, out ulong length) { if (col.metaType.IsLong && !col.metaType.IsPlp) { // // we don't care about TextPtrs, simply go after the data after it // byte textPtrLen; if (!stateObj.TryReadByte(out textPtrLen)) { isNull = false; length = 0; return false; } if (0 != textPtrLen) { // read past text pointer if (!stateObj.TrySkipBytes(textPtrLen)) { isNull = false; length = 0; return false; } // read past timestamp if (!stateObj.TrySkipBytes(TdsEnums.TEXT_TIME_STAMP_LEN)) { isNull = false; length = 0; return false; } isNull = false; return TryGetDataLength(col, stateObj, out length); } else { isNull = true; length = 0; return true; } } else { // non-blob columns ulong longlen; if (!TryGetDataLength(col, stateObj, out longlen)) { isNull = false; length = 0; return false; } isNull = IsNull(col.metaType, longlen); length = (isNull ? 0 : longlen); return true; } }
/// <summary> /// This method skips bytes of a single column value from the media. It supports NBCROW and handles all types of values, including PLP and long /// </summary> internal bool TrySkipValue(SqlMetaDataPriv md, int columnOrdinal, TdsParserStateObject stateObj) { if (stateObj.IsNullCompressionBitSet(columnOrdinal)) { return true; } if (md.metaType.IsPlp) { ulong ignored; if (!TrySkipPlpValue(UInt64.MaxValue, stateObj, out ignored)) { return false; } } else if (md.metaType.IsLong) { Debug.Assert(!md.metaType.IsPlp, "Plp types must be handled using SkipPlpValue"); byte textPtrLen; if (!stateObj.TryReadByte(out textPtrLen)) { return false; } if (0 != textPtrLen) { if (!stateObj.TrySkipBytes(textPtrLen + TdsEnums.TEXT_TIME_STAMP_LEN)) { return false; } int length; if (!TryGetTokenLength(md.tdsType, stateObj, out length)) { return false; } if (!stateObj.TrySkipBytes(length)) { return false; } } } else { int length; if (!TryGetTokenLength(md.tdsType, stateObj, out length)) { return false; } // if false, no value to skip - it's null if (!IsNull(md.metaType, (ulong)length)) { if (!stateObj.TrySkipBytes(length)) { return false; } } } return true; }
internal bool TryProcessAltMetaData(int cColumns, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData) { Debug.Assert(cColumns > 0, "should have at least 1 column in altMetaData!"); metaData = null; _SqlMetaDataSet altMetaDataSet = new _SqlMetaDataSet(cColumns); int[] indexMap = new int[cColumns]; if (!stateObj.TryReadUInt16(out altMetaDataSet.id)) { return false; } byte byCols; if (!stateObj.TryReadByte(out byCols)) { return false; } while (byCols > 0) { if (!stateObj.TrySkipBytes(2)) { // ignore ColNum ... return false; } byCols--; } // pass 1, read the meta data off the wire for (int i = 0; i < cColumns; i++) { // internal meta data class _SqlMetaData col = altMetaDataSet[i]; byte op; if (!stateObj.TryReadByte(out op)) { return false; } ushort operand; if (!stateObj.TryReadUInt16(out operand)) { return false; } if (!TryCommonProcessMetaData(stateObj, col)) { return false; } indexMap[i] = i; } altMetaDataSet.indexMap = indexMap; altMetaDataSet.visibleColumns = cColumns; metaData = altMetaDataSet; return true; }
// augments current metadata with table and key information private bool TryProcessColInfo(_SqlMetaDataSet columns, SqlDataReader reader, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData) { Debug.Assert(columns != null && columns.Length > 0, "no metadata available!"); metaData = null; for (int i = 0; i < columns.Length; i++) { _SqlMetaData col = columns[i]; byte ignored; if (!stateObj.TryReadByte(out ignored)) { // colnum, ignore return false; } if (!stateObj.TryReadByte(out ignored)) { // tablenum, ignore return false; } // interpret status byte status; if (!stateObj.TryReadByte(out status)) { return false; } col.isKey = (TdsEnums.SQLKey == (status & TdsEnums.SQLKey)); col.isHidden = (TdsEnums.SQLHidden == (status & TdsEnums.SQLHidden)); // read off the base table name if it is different than the select list column name if (TdsEnums.SQLDifferentName == (status & TdsEnums.SQLDifferentName)) { byte len; if (!stateObj.TryReadByte(out len)) { return false; } if (!stateObj.TrySkipBytes(len * ADP.CharSize)) { return false; } } if (TdsEnums.SQLExpression == (status & TdsEnums.SQLExpression)) { col.updatability = 0; } } // set the metadata so that the stream knows some metadata info has changed metaData = columns; return true; }
private bool TryProcessLoginAck(TdsParserStateObject stateObj, out SqlLoginAck sqlLoginAck) { SqlLoginAck a = new SqlLoginAck(); sqlLoginAck = null; // read past interface type and version if (!stateObj.TrySkipBytes(1)) { return false; } byte[] b = new byte[TdsEnums.VERSION_SIZE]; if (!stateObj.TryReadByteArray(b, 0, b.Length)) { return false; } a.tdsVersion = (UInt32)((((((b[0] << 8) | b[1]) << 8) | b[2]) << 8) | b[3]); // bytes are in motorola order (high byte first) UInt32 majorMinor = a.tdsVersion & 0xff00ffff; UInt32 increment = (a.tdsVersion >> 16) & 0xff; // Server responds: // 0x07000000 -> Sphinx // Notice server response format is different for bwd compat // 0x07010000 -> Shiloh RTM // Notice server response format is different for bwd compat // 0x71000001 -> Shiloh SP1 // 0x72xx0002 -> Yukon RTM // information provided by S. Ashwin switch (majorMinor) { case TdsEnums.YUKON_MAJOR << 24 | TdsEnums.YUKON_RTM_MINOR: // Yukon if (increment != TdsEnums.YUKON_INCREMENT) { throw SQL.InvalidTDSVersion(); } break; case TdsEnums.KATMAI_MAJOR << 24 | TdsEnums.KATMAI_MINOR: if (increment != TdsEnums.KATMAI_INCREMENT) { throw SQL.InvalidTDSVersion(); } _isKatmai = true; break; case TdsEnums.DENALI_MAJOR << 24 | TdsEnums.DENALI_MINOR: if (increment != TdsEnums.DENALI_INCREMENT) { throw SQL.InvalidTDSVersion(); } _isDenali = true; break; default: throw SQL.InvalidTDSVersion(); } _isKatmai |= _isDenali; stateObj._outBytesUsed = stateObj._outputHeaderLen; byte len; if (!stateObj.TryReadByte(out len)) { return false; } if (!stateObj.TrySkipBytes(len * ADP.CharSize)) { return false; } if (!stateObj.TryReadByte(out a.majorVersion)) { return false; } if (!stateObj.TryReadByte(out a.minorVersion)) { return false; } byte buildNumHi, buildNumLo; if (!stateObj.TryReadByte(out buildNumHi)) { return false; } if (!stateObj.TryReadByte(out buildNumLo)) { return false; } a.buildNum = (short)((buildNumHi << 8) + buildNumLo); Debug.Assert(_state == TdsParserState.OpenNotLoggedIn, "ProcessLoginAck called with state not TdsParserState.OpenNotLoggedIn"); _state = TdsParserState.OpenLoggedIn; { if (_fMARS) { _resetConnectionEvent = new AutoResetEvent(true); } } // Fail if SSE UserInstance and we have not received this info. if (_connHandler.ConnectionOptions.UserInstance && ADP.IsEmpty(_connHandler.InstanceName)) { stateObj.AddError(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, Server, SQLMessage.UserInstanceFailure(), "", 0)); ThrowExceptionAndWarning(stateObj); } sqlLoginAck = a; return true; }
private bool TryProcessSessionState(TdsParserStateObject stateObj, int length, SessionData sdata) { if (length < 5) { throw SQL.ParsingError(); } UInt32 seqNum; if (!stateObj.TryReadUInt32(out seqNum)) { return false; } if (seqNum == UInt32.MaxValue) { _connHandler.DoNotPoolThisConnection(); } byte status; if (!stateObj.TryReadByte(out status)) { return false; } if (status > 1) { throw SQL.ParsingError(); } bool recoverable = status != 0; length -= 5; while (length > 0) { byte stateId; if (!stateObj.TryReadByte(out stateId)) { return false; } int stateLen; byte stateLenByte; if (!stateObj.TryReadByte(out stateLenByte)) { return false; } if (stateLenByte < 0xFF) { stateLen = stateLenByte; } else { if (!stateObj.TryReadInt32(out stateLen)) { return false; } } byte[] buffer = null; lock (sdata._delta) { if (sdata._delta[stateId] == null) { buffer = new byte[stateLen]; sdata._delta[stateId] = new SessionStateRecord { _version = seqNum, _dataLength = stateLen, _data = buffer, _recoverable = recoverable }; sdata._deltaDirty = true; if (!recoverable) { checked { sdata._unrecoverableStatesCount++; } } } else { if (sdata._delta[stateId]._version <= seqNum) { SessionStateRecord sv = sdata._delta[stateId]; sv._version = seqNum; sv._dataLength = stateLen; if (sv._recoverable != recoverable) { if (recoverable) { Debug.Assert(sdata._unrecoverableStatesCount > 0, "Unrecoverable states count >0"); sdata._unrecoverableStatesCount--; } else { checked { sdata._unrecoverableStatesCount++; } } sv._recoverable = recoverable; } buffer = sv._data; if (buffer.Length < stateLen) { buffer = new byte[stateLen]; sv._data = buffer; } } } } if (buffer != null) { if (!stateObj.TryReadByteArray(buffer, 0, stateLen)) { return false; } } else { if (!stateObj.TrySkipBytes(stateLen)) return false; } if (stateLenByte < 0xFF) { length -= 2 + stateLen; } else { length -= 6 + stateLen; } } sdata.AssertUnrecoverableStateCountIsCorrect(); return true; }
private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange[] sqlEnvChange) { // There could be multiple environment change messages following this token. byte byteLength; int processedLength = 0; int nvalues = 0; SqlEnvChange[] envarray = new SqlEnvChange[3]; // Why is this hardcoded to 3? sqlEnvChange = null; while (tokenLength > processedLength) { if (nvalues >= envarray.Length) { // This is a rare path. Most of the time we will have 1 or 2 envchange data streams. SqlEnvChange[] newenvarray = new SqlEnvChange[envarray.Length + 3]; for (int ii = 0; ii < envarray.Length; ii++) newenvarray[ii] = envarray[ii]; envarray = newenvarray; } SqlEnvChange env = new SqlEnvChange(); if (!stateObj.TryReadByte(out env.type)) { return false; } envarray[nvalues] = env; nvalues++; switch (env.type) { case TdsEnums.ENV_DATABASE: case TdsEnums.ENV_LANG: if (!TryReadTwoStringFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_CHARSET: // we copied this behavior directly from luxor - see charset envchange // section from sqlctokn.c if (!TryReadTwoStringFields(env, stateObj)) { return false; } if (env.newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) { _defaultCodePage = TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE; _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } else { Debug.Assert(env.newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10"); string stringCodePage = env.newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET); _defaultCodePage = Int32.Parse(stringCodePage, NumberStyles.Integer, CultureInfo.InvariantCulture); _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } break; case TdsEnums.ENV_PACKETSIZE: // take care of packet size right here Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); if (!TryReadTwoStringFields(env, stateObj)) { // Changing packet size does not support retry, should not pend" throw SQL.SynchronousCallMayNotPend(); } // Only set on physical state object - this should only occur on LoginAck prior // to MARS initialization! Int32 packetSize = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); if (_physicalStateObj.SetPacketSize(packetSize)) { // If packet size changed, we need to release our SNIPackets since // those are tied to packet size of connection. _physicalStateObj.ClearAllWritePackets(); // Update SNI ConsumerInfo value to be resulting packet size UInt32 unsignedPacketSize = (UInt32)packetSize; UInt32 result = SNINativeMethodWrapper.SNISetInfo(_physicalStateObj.Handle, SNINativeMethodWrapper.QTypes.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo"); } break; case TdsEnums.ENV_LOCALEID: if (!TryReadTwoStringFields(env, stateObj)) { return false; } _defaultLCID = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); break; case TdsEnums.ENV_COMPFLAGS: if (!TryReadTwoStringFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_COLLATION: Debug.Assert(env.newLength == 5 || env.newLength == 0, "Improper length in new collation!"); if (!stateObj.TryReadByte(out byteLength)) { return false; } env.newLength = byteLength; if (env.newLength == 5) { if (!TryProcessCollation(stateObj, out env.newCollation)) { return false; } // give the parser the new collation values in case parameters don't specify one _defaultCollation = env.newCollation; int newCodePage = GetCodePage(env.newCollation, stateObj); if (newCodePage != _defaultCodePage) { _defaultCodePage = newCodePage; _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } _defaultLCID = env.newCollation.LCID; } if (!stateObj.TryReadByte(out byteLength)) { return false; } env.oldLength = byteLength; Debug.Assert(env.oldLength == 5 || env.oldLength == 0, "Improper length in old collation!"); if (env.oldLength == 5) { if (!TryProcessCollation(stateObj, out env.oldCollation)) { return false; } } env.length = 3 + env.newLength + env.oldLength; break; case TdsEnums.ENV_BEGINTRAN: case TdsEnums.ENV_COMMITTRAN: case TdsEnums.ENV_ROLLBACKTRAN: if (!stateObj.TryReadByte(out byteLength)) { return false; } env.newLength = byteLength; Debug.Assert(env.newLength == 0 || env.newLength == 8, "Improper length for new transaction id!"); if (env.newLength > 0) { if (!stateObj.TryReadInt64(out env.newLongValue)) { return false; } Debug.Assert(env.newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id. } else { env.newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. } if (!stateObj.TryReadByte(out byteLength)) { return false; } env.oldLength = byteLength; Debug.Assert(env.oldLength == 0 || env.oldLength == 8, "Improper length for old transaction id!"); if (env.oldLength > 0) { if (!stateObj.TryReadInt64(out env.oldLongValue)) { return false; } Debug.Assert(env.oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id. } else { env.oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. } // env.length includes 1 byte type token env.length = 3 + env.newLength + env.oldLength; break; case TdsEnums.ENV_LOGSHIPNODE: // env.newBinValue is secondary node, env.oldBinValue is witness node // comes before LoginAck so we can't assert this if (!TryReadTwoStringFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_SPRESETCONNECTIONACK: if (!TryReadTwoBinaryFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_USERINSTANCE: if (!TryReadTwoStringFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_ROUTING: ushort newLength; if (!stateObj.TryReadUInt16(out newLength)) { return false; } env.newLength = newLength; byte protocol; if (!stateObj.TryReadByte(out protocol)) { return false; } ushort port; if (!stateObj.TryReadUInt16(out port)) { return false; } UInt16 serverLen; if (!stateObj.TryReadUInt16(out serverLen)) { return false; } string serverName; if (!stateObj.TryReadString(serverLen, out serverName)) { return false; } env.newRoutingInfo = new RoutingInfo(protocol, port, serverName); UInt16 oldLength; if (!stateObj.TryReadUInt16(out oldLength)) { return false; } if (!stateObj.TrySkipBytes(oldLength)) { return false; } env.length = env.newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength] break; // ENVCHANGE tokens not supported by CoreCLR case TdsEnums.ENV_ENLISTDTC: case TdsEnums.ENV_DEFECTDTC: case TdsEnums.ENV_TRANSACTIONENDED: case TdsEnums.ENV_PROMOTETRANSACTION: case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS: throw SQL.UnsupportedFeatureAndToken(_connHandler, ((TdsEnums.EnvChangeType)env.type).ToString()); default: Debug.Assert(false, "Unknown environment change token: " + env.type); break; } processedLength += env.length; } sqlEnvChange = envarray; return true; }
// Main parse loop for the top-level tds tokens, calls back into the I*Handler interfaces 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 recieved 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; }
internal bool TryProcessAltMetaData(int cColumns, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData) { Debug.Assert(cColumns > 0, "should have at least 1 column in altMetaData!"); metaData = null; _SqlMetaDataSet altMetaDataSet = new _SqlMetaDataSet(cColumns, null); int[] indexMap = new int[cColumns]; if (!stateObj.TryReadUInt16(out altMetaDataSet.id)) { return false; } byte byCols; if (!stateObj.TryReadByte(out byCols)) { return false; } while (byCols > 0) { if (!stateObj.TrySkipBytes(2)) { // ignore ColNum ... return false; } byCols--; } // pass 1, read the meta data off the wire for (int i = 0; i < cColumns; i++) { // internal meta data class _SqlMetaData col = altMetaDataSet[i]; if (!stateObj.TryReadByte(out col.op)) { return false; } if (!stateObj.TryReadUInt16(out col.operand)) { return false; } // TCE is not applicable to AltMetadata. if (!TryCommonProcessMetaData(stateObj, col, null, fColMD: false, columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Disabled)) { return false; } if (ADP.IsEmpty(col.column)) { // create column name from op switch (col.op) { case TdsEnums.AOPAVG: col.column = "avg"; break; case TdsEnums.AOPCNT: col.column = "cnt"; break; case TdsEnums.AOPCNTB: col.column = "cntb"; break; case TdsEnums.AOPMAX: col.column = "max"; break; case TdsEnums.AOPMIN: col.column = "min"; break; case TdsEnums.AOPSUM: col.column = "sum"; break; case TdsEnums.AOPANY: col.column = "any"; break; case TdsEnums.AOPNOOP: col.column = "noop"; break; case TdsEnums.AOPSTDEV: col.column = "stdev"; break; case TdsEnums.AOPSTDEVP: col.column = "stdevp"; break; case TdsEnums.AOPVAR: col.column = "var"; break; case TdsEnums.AOPVARP: col.column = "varp"; break; } } indexMap[i] = i; } altMetaDataSet.indexMap = indexMap; altMetaDataSet.visibleColumns = cColumns; metaData = altMetaDataSet; return true; }