Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data / System / Data / SqlClient / TdsParser.cs
blob3d3d95f3f6d93ff9fd11185a60c10fa0af2658b4
1 //------------------------------------------------------------------------------
2 // <copyright file="TdsParser.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data.SqlClient {
10 using System;
11 using System.Collections.Generic;
12 using System.Data.Common;
13 using System.Data.ProviderBase;
14 using System.Data.Sql;
15 using System.Data.SqlTypes;
16 using System.Diagnostics;
17 using System.Globalization;
18 using System.IO;
19 using System.Runtime.CompilerServices;
20 using System.Runtime.InteropServices;
21 using System.Text;
22 using System.Threading;
23 using System.Threading.Tasks;
24 using System.Xml;
26 using MSS = Microsoft.SqlServer.Server;
28 // The TdsParser Object controls reading/writing to the netlib, parsing the tds,
29 // and surfacing objects to the user.
30 sealed internal class TdsParser {
31 private static int _objectTypeCount; // Bid counter
32 internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
34 static Task completedTask;
35 static Task CompletedTask {
36 get {
37 if (completedTask == null) {
38 completedTask = Task.FromResult<object>(null);
40 return completedTask;
44 internal int ObjectID {
45 get {
46 return _objectID;
51 // ReliabilitySection Usage:
53 // #if DEBUG
54 // TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
56 // RuntimeHelpers.PrepareConstrainedRegions();
57 // try {
58 // tdsReliabilitySection.Start();
59 // #else
60 // {
61 // #endif //DEBUG
63 // // code that requires reliability
65 // }
66 // #if DEBUG
67 // finally {
68 // tdsReliabilitySection.Stop();
69 // }
70 // #endif //DEBUG
72 internal struct ReliabilitySection {
73 #if DEBUG
74 // do not allocate TLS data in RETAIL bits
75 [ThreadStatic]
76 private static int s_reliabilityCount; // initialized to 0 by CLR
78 private bool m_started; // initialized to false (not started) by CLR
79 #endif //DEBUG
81 [Conditional("DEBUG")]
82 internal void Start() {
83 #if DEBUG
84 Debug.Assert(!m_started);
86 RuntimeHelpers.PrepareConstrainedRegions();
87 try {
89 finally {
90 ++s_reliabilityCount;
91 m_started = true;
93 #endif //DEBUG
96 [Conditional("DEBUG")]
97 internal void Stop() {
98 #if DEBUG
99 // cannot assert m_started - ThreadAbortException can be raised before Start is called
101 if (m_started) {
102 Debug.Assert(s_reliabilityCount > 0);
104 RuntimeHelpers.PrepareConstrainedRegions();
105 try {
107 finally {
108 --s_reliabilityCount;
109 m_started = false;
112 #endif //DEBUG
115 // you need to setup for a thread abort somewhere before you call this method
116 [Conditional("DEBUG")]
117 internal static void Assert(string message) {
118 #if DEBUG
119 Debug.Assert(s_reliabilityCount > 0, message);
120 #endif //DEBUG
124 // Default state object for parser
125 internal TdsParserStateObject _physicalStateObj = null; // Default stateObj and connection for Dbnetlib and non-MARS SNI.
127 // Also, default logical stateObj and connection for MARS over SNI.
128 internal TdsParserStateObject _pMarsPhysicalConObj = null; // With MARS enabled, cached physical stateObj and connection.
130 // Must keep this around - especially for callbacks on pre-MARS
131 // ReadAsync which will return if physical connection broken!
133 // Per Instance TDS Parser variables
136 // Constants
137 const int constBinBufferSize = 4096; // Size of the buffer used to read input parameter of type Stream
138 const int constTextBufferSize = 4096; // Size of the buffer (in chars) user to read input parameter of type TextReader
140 // State variables
141 internal TdsParserState _state = TdsParserState.Closed; // status flag for connection
143 private string _server = ""; // name of server that the parser connects to
145 internal volatile bool _fResetConnection = false; // flag to denote whether we are needing to call sp_reset
146 internal volatile bool _fPreserveTransaction = false; // flag to denote whether we need to preserve the transaction when reseting
148 private SqlCollation _defaultCollation; // default collation from the server
150 private int _defaultCodePage;
152 private int _defaultLCID;
154 internal Encoding _defaultEncoding = null; // for sql character data
156 private static EncryptionOptions _sniSupportedEncryptionOption = SNILoadHandle.SingletonInstance.Options;
158 private EncryptionOptions _encryptionOption = _sniSupportedEncryptionOption;
160 private SqlInternalTransaction _currentTransaction;
161 private SqlInternalTransaction _pendingTransaction; // pending transaction for Yukon and beyond.
162 // SQLHOT 483
163 // need to hold on to the transaction id if distributed transaction merely rolls back without defecting.
164 private long _retainedTransactionId = SqlInternalTransaction.NullTransactionId;
166 // This counter is used for the entire connection to track the open result count for all
167 // operations not under a transaction.
168 private int _nonTransactedOpenResultCount = 0;
170 // Connection reference
171 private SqlInternalConnectionTds _connHandler;
173 // Async/Mars variables
174 private bool _fMARS = false;
176 internal bool _loginWithFailover = false; // set to true while connect in failover mode so parser state object can adjust its logic
178 internal AutoResetEvent _resetConnectionEvent = null; // Used to serialize executes and call reset on first execute only.
180 internal TdsParserSessionPool _sessionPool = null; // initialized only when we're a MARS parser.
182 // Version variables
183 private bool _isShiloh = false; // set to true if we connect to a 8.0 server (SQL 2000) or later
185 private bool _isShilohSP1 = false; // set to true if speaking to Shiloh SP1 or later
187 private bool _isYukon = false; // set to true if speaking to Yukon or later
189 private bool _isKatmai = false;
191 private bool _isDenali = false;
193 private byte[] _sniSpnBuffer = null;
197 // SqlStatistics
198 private SqlStatistics _statistics = null;
200 private bool _statisticsIsInTransaction = false;
203 // STATIC TDS Parser variables
206 // NIC address caching
207 private static byte[] s_nicAddress; // cache the NIC address from the registry
209 // SSPI variables
210 private static bool s_fSSPILoaded = false; // bool to indicate whether library has been loaded
212 private volatile static UInt32 s_maxSSPILength = 0; // variable to hold max SSPI data size, keep for token from server
214 // ADAL variables
215 private static bool s_fADALLoaded = false; // bool to indicate whether ADAL library has been loaded
217 // textptr sequence
218 private static readonly byte[] s_longDataHeader = { 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
220 private static object s_tdsParserLock = new object();
222 // Various other statics
223 private const int ATTENTION_TIMEOUT = 5000; // internal attention timeout, in ticks
225 // XML metadata substitue sequence
226 private static readonly byte[] s_xmlMetadataSubstituteSequence = { 0xe7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 };
228 // size of Guid (e.g. _clientConnectionId, ActivityId.Id)
229 private const int GUID_SIZE = 16;
231 // NOTE: You must take the internal connection's _parserLock before modifying this
232 internal bool _asyncWrite = false;
234 // TCE supported flag, used to determine if new TDS fields are present. This is
235 // useful when talking to downlevel/uplevel server.
236 private bool _serverSupportsColumnEncryption = false;
238 /// <summary>
239 /// Get or set if column encryption is supported by the server.
240 /// </summary>
241 internal bool IsColumnEncryptionSupported {
242 get {
243 return _serverSupportsColumnEncryption;
245 set {
246 _serverSupportsColumnEncryption = value;
250 internal TdsParser(bool MARS, bool fAsynchronous) {
251 _fMARS = MARS; // may change during Connect to pre Yukon servers
252 _physicalStateObj = new TdsParserStateObject(this);
255 internal SqlInternalConnectionTds Connection {
256 get {
257 return _connHandler;
261 internal SqlInternalTransaction CurrentTransaction {
262 get {
263 return _currentTransaction;
265 set {
266 Debug.Assert(value == _currentTransaction
267 || null == _currentTransaction
268 || null == value
269 || (null != _currentTransaction && !_currentTransaction.IsLocal), "attempting to change current transaction?");
271 // If there is currently a transaction active, we don't want to
272 // change it; this can occur when there is a delegated transaction
273 // and the user attempts to do an API begin transaction; in these
274 // cases, it's safe to ignore the set.
275 if ((null == _currentTransaction && null != value)
276 ||(null != _currentTransaction && null == value)) {
277 _currentTransaction = value;
282 internal int DefaultLCID {
283 get {
284 return _defaultLCID;
288 internal EncryptionOptions EncryptionOptions {
289 get {
290 return _encryptionOption;
292 set {
293 _encryptionOption = value;
297 internal bool IsYukonOrNewer {
298 get {
299 return _isYukon;
303 internal bool IsKatmaiOrNewer {
304 get {
305 return _isKatmai;
309 internal bool MARSOn {
310 get {
311 return _fMARS;
315 internal SqlInternalTransaction PendingTransaction {
316 get {
317 return _pendingTransaction;
319 set {
320 Debug.Assert (null != value, "setting a non-null PendingTransaction?");
321 _pendingTransaction = value;
325 internal string Server {
326 get {
327 return _server;
331 internal TdsParserState State {
332 get {
333 return _state;
335 set {
336 _state = value;
340 internal SqlStatistics Statistics {
341 get {
342 return _statistics;
344 set {
345 _statistics = value;
349 private bool IncludeTraceHeader {
350 get {
351 return (_isDenali && Bid.TraceOn && Bid.IsOn(ActivityCorrelator.CorrelationTracePoints));
357 internal int IncrementNonTransactedOpenResultCount() {
358 // IMPORTANT - this increments the connection wide open result count for all
359 // operations not under a transaction! Do not call if you intend to modify the
360 // count for a transaction!
361 Debug.Assert(_nonTransactedOpenResultCount >= 0, "Unexpected result count state");
362 int result = Interlocked.Increment(ref _nonTransactedOpenResultCount);
363 return result;
366 internal void DecrementNonTransactedOpenResultCount() {
367 // IMPORTANT - this decrements the connection wide open result count for all
368 // operations not under a transaction! Do not call if you intend to modify the
369 // count for a transaction!
370 Interlocked.Decrement(ref _nonTransactedOpenResultCount);
371 Debug.Assert(_nonTransactedOpenResultCount >= 0, "Unexpected result count state");
374 internal void ProcessPendingAck(TdsParserStateObject stateObj) {
375 if (stateObj._attentionSent) {
376 ProcessAttention(stateObj);
380 internal void Connect(ServerInfo serverInfo,
381 SqlInternalConnectionTds connHandler,
382 bool ignoreSniOpenTimeout,
383 long timerExpire,
384 bool encrypt,
385 bool trustServerCert,
386 bool integratedSecurity,
387 bool withFailover,
388 bool isFirstTransparentAttempt,
389 SqlAuthenticationMethod authType,
390 bool disableTnir) {
391 if (_state != TdsParserState.Closed) {
392 Debug.Assert(false, "TdsParser.Connect called on non-closed connection!");
393 return;
396 _connHandler = connHandler;
397 _loginWithFailover = withFailover;
399 UInt32 sniStatus = SNILoadHandle.SingletonInstance.SNIStatus;
400 if (sniStatus != TdsEnums.SNI_SUCCESS) {
401 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
402 _physicalStateObj.Dispose();
403 ThrowExceptionAndWarning(_physicalStateObj);
404 Debug.Assert(false, "SNI returned status != success, but no error thrown?");
407 //Create LocalDB instance if necessary
408 if (connHandler.ConnectionOptions.LocalDBInstance != null)
409 LocalDBAPI.CreateLocalDBInstance(connHandler.ConnectionOptions.LocalDBInstance);
411 if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) {
412 LoadSSPILibrary();
413 // now allocate proper length of buffer
414 _sniSpnBuffer = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength];
415 Bid.Trace("<sc.TdsParser.Connect|SEC> SSPI or Active Directory Authentication Library for SQL Server based integrated authentication\n");
417 else {
418 _sniSpnBuffer = null;
419 if (authType == SqlAuthenticationMethod.ActiveDirectoryPassword) {
420 Bid.Trace("<sc.TdsParser.Connect|SEC> Active Directory Password authentication\n");
422 else if (authType == SqlAuthenticationMethod.SqlPassword) {
423 Bid.Trace("<sc.TdsParser.Connect|SEC> SQL Password authentication\n");
425 else{
426 Bid.Trace("<sc.TdsParser.Connect|SEC> SQL authentication\n");
430 byte[] instanceName = null;
432 Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point.");
433 _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
434 _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.InitializeConnection);
436 bool fParallel = _connHandler.ConnectionOptions.MultiSubnetFailover;
438 TransparentNetworkResolutionState transparentNetworkResolutionState;
439 if (_connHandler.ConnectionOptions.TransparentNetworkIPResolution && !disableTnir)
441 if(isFirstTransparentAttempt)
442 transparentNetworkResolutionState = TransparentNetworkResolutionState.SequentialMode;
443 else
444 transparentNetworkResolutionState = TransparentNetworkResolutionState.ParallelMode;
446 else
447 transparentNetworkResolutionState = TransparentNetworkResolutionState.DisabledMode;
449 int totalTimeout = _connHandler.ConnectionOptions.ConnectTimeout;
451 _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire,
452 out instanceName, _sniSpnBuffer, false, true, fParallel, transparentNetworkResolutionState, totalTimeout);
454 if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) {
455 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
457 // Since connect failed, free the unmanaged connection memory.
458 // HOWEVER - only free this after the netlib error was processed - if you
459 // don't, the memory for the connection object might not be accurate and thus
460 // a bad error could be returned (as it was when it was freed to early for me).
461 _physicalStateObj.Dispose();
462 Bid.Trace("<sc.TdsParser.Connect|ERR|SEC> Login failure\n");
463 ThrowExceptionAndWarning(_physicalStateObj);
464 Debug.Assert(false, "SNI returned status != success, but no error thrown?");
467 _server = serverInfo.ResolvedServerName;
469 if (null != connHandler.PoolGroupProviderInfo) {
470 // If we are pooling, check to see if we were processing an
471 // alias which has changed, which means we need to clean out
472 // the pool. See Webdata 104293.
473 // This should not apply to routing, as it is not an alias change, routed connection
474 // should still use VNN of AlwaysOn cluster as server for pooling purposes.
475 connHandler.PoolGroupProviderInfo.AliasCheck(serverInfo.PreRoutingServerName==null ?
476 serverInfo.ResolvedServerName: serverInfo.PreRoutingServerName);
478 _state = TdsParserState.OpenNotLoggedIn;
479 _physicalStateObj.SniContext = SniContext.Snix_PreLoginBeforeSuccessfullWrite; // SQL BU DT 376766
480 _physicalStateObj.TimeoutTime = timerExpire;
482 bool marsCapable = false;
484 _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.InitializeConnection);
485 _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake);
487 UInt32 result = SNINativeMethodWrapper.SniGetConnectionId(_physicalStateObj.Handle, ref _connHandler._clientConnectionId);
488 Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId");
491 Bid.Trace("<sc.TdsParser.Connect|SEC> Sending prelogin handshake\n");
492 SendPreLoginHandshake(instanceName, encrypt);
494 _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake);
495 _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake);
497 _physicalStateObj.SniContext = SniContext.Snix_PreLogin;
499 Bid.Trace("<sc.TdsParser.Connect|SEC> Consuming prelogin handshake\n");
500 PreLoginHandshakeStatus status = ConsumePreLoginHandshake(authType, encrypt, trustServerCert, integratedSecurity, out marsCapable,
501 out _connHandler._fedAuthRequired);
503 if (status == PreLoginHandshakeStatus.InstanceFailure) {
504 Bid.Trace("<sc.TdsParser.Connect|SEC> Prelogin handshake unsuccessful. Reattempting prelogin handshake\n");
505 _physicalStateObj.Dispose(); // Close previous connection
507 // On Instance failure re-connect and flush SNI named instance cache.
508 _physicalStateObj.SniContext=SniContext.Snix_Connect;
509 _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, _sniSpnBuffer, true, true, fParallel, transparentNetworkResolutionState, totalTimeout);
511 if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) {
512 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
513 Bid.Trace("<sc.TdsParser.Connect|ERR|SEC> Login failure\n");
514 ThrowExceptionAndWarning(_physicalStateObj);
517 UInt32 retCode = SNINativeMethodWrapper.SniGetConnectionId(_physicalStateObj.Handle, ref _connHandler._clientConnectionId);
518 Debug.Assert(retCode == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId");
520 Bid.Trace("<sc.TdsParser.Connect|SEC> Sending prelogin handshake\n");
521 SendPreLoginHandshake(instanceName, encrypt);
522 status = ConsumePreLoginHandshake(authType, encrypt, trustServerCert, integratedSecurity, out marsCapable,
523 out _connHandler._fedAuthRequired);
525 // Don't need to check for Sphinx failure, since we've already consumed
526 // one pre-login packet and know we are connecting to Shiloh.
527 if (status == PreLoginHandshakeStatus.InstanceFailure) {
528 Bid.Trace("<sc.TdsParser.Connect|ERR|SEC> Prelogin handshake unsuccessful. Login failure\n");
529 throw SQL.InstanceFailure();
532 Bid.Trace("<sc.TdsParser.Connect|SEC> Prelogin handshake successful\n");
534 if (_fMARS && marsCapable) {
535 // if user explictly disables mars or mars not supported, don't create the session pool
536 _sessionPool = new TdsParserSessionPool(this);
538 else {
539 _fMARS = false;
542 if (authType == SqlAuthenticationMethod.ActiveDirectoryPassword || (authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _connHandler._fedAuthRequired)) {
543 Debug.Assert(!integratedSecurity, "The legacy Integrated Security connection string option cannot be true when using Active Directory Authentication Library for SQL Server Based workflows.");
545 LoadADALLibrary();
546 if (Bid.AdvancedOn) {
547 Bid.Trace("<sc.TdsParser.Connect|SEC> Active directory authentication.Loaded Active Directory Authentication Library for SQL Server\n");
551 return;
554 internal void RemoveEncryption() {
555 Debug.Assert(_encryptionOption == EncryptionOptions.LOGIN, "Invalid encryption option state");
557 UInt32 error = 0;
559 // Remove SSL (Encryption) SNI provider since we only wanted to encrypt login.
560 error = SNINativeMethodWrapper.SNIRemoveProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.SSL_PROV);
561 if (error != TdsEnums.SNI_SUCCESS) {
562 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
563 ThrowExceptionAndWarning(_physicalStateObj);
565 // create a new packet encryption changes the internal packet size Bug# 228403
566 try {} // EmptyTry/Finally to avoid FXCop violation
567 finally {
568 _physicalStateObj.ClearAllWritePackets();
572 internal void EnableMars() {
573 if (_fMARS) {
574 // Cache physical stateObj and connection.
575 _pMarsPhysicalConObj = _physicalStateObj;
577 UInt32 error = 0;
578 UInt32 info = 0;
580 // Add SMUX (MARS) SNI provider.
581 error = SNINativeMethodWrapper.SNIAddProvider(_pMarsPhysicalConObj.Handle, SNINativeMethodWrapper.ProviderEnum.SMUX_PROV, ref info);
583 if (error != TdsEnums.SNI_SUCCESS) {
584 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
585 ThrowExceptionAndWarning(_physicalStateObj);
588 // HACK HACK HACK - for Async only
589 // Have to post read to intialize MARS - will get callback on this when connection goes
590 // down or is closed.
592 IntPtr temp = IntPtr.Zero;
594 RuntimeHelpers.PrepareConstrainedRegions();
595 try {} finally {
596 _pMarsPhysicalConObj.IncrementPendingCallbacks();
598 error = SNINativeMethodWrapper.SNIReadAsync(_pMarsPhysicalConObj.Handle, ref temp);
600 if (temp != IntPtr.Zero) {
601 // Be sure to release packet, otherwise it will be leaked by native.
602 SNINativeMethodWrapper.SNIPacketRelease(temp);
605 Debug.Assert(IntPtr.Zero == temp, "unexpected syncReadPacket without corresponding SNIPacketRelease");
606 if (TdsEnums.SNI_SUCCESS_IO_PENDING != error) {
607 Debug.Assert(TdsEnums.SNI_SUCCESS != error, "Unexpected successfull read async on physical connection before enabling MARS!");
608 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
609 ThrowExceptionAndWarning(_physicalStateObj);
612 _physicalStateObj = CreateSession(); // Create and open default MARS stateObj and connection.
616 internal TdsParserStateObject CreateSession() {
617 TdsParserStateObject session = new TdsParserStateObject(this, (SNIHandle)_pMarsPhysicalConObj.Handle, true);
618 if (Bid.AdvancedOn) {
619 Bid.Trace("<sc.TdsParser.CreateSession|ADV> %d# created session %d\n", ObjectID, session.ObjectID);
621 return session;
624 internal TdsParserStateObject GetSession(object owner) {
625 TdsParserStateObject session = null;
629 if (MARSOn) {
630 session = _sessionPool.GetSession(owner);
632 Debug.Assert(!session._pendingData, "pending data on a pooled MARS session");
633 if (Bid.AdvancedOn) {
634 Bid.Trace("<sc.TdsParser.GetSession|ADV> %d# getting session %d from pool\n", ObjectID, session.ObjectID);
637 else {
638 session = _physicalStateObj;
639 if (Bid.AdvancedOn) {
640 Bid.Trace("<sc.TdsParser.GetSession|ADV> %d# getting physical session %d\n", ObjectID, session.ObjectID);
643 Debug.Assert(session._outputPacketNumber==1, "The packet number is expected to be 1");
644 return session;
647 internal void PutSession(TdsParserStateObject session) {
648 session.AssertStateIsClean();
650 if (MARSOn) {
651 // This will take care of disposing if the parser is closed
652 _sessionPool.PutSession(session);
654 else if ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken)) {
655 // Parser is closed\broken - dispose the stateObj
656 Debug.Assert(session == _physicalStateObj, "MARS is off, but session to close is not the _physicalStateObj");
657 _physicalStateObj.SniContext = SniContext.Snix_Close;
658 #if DEBUG
659 _physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
660 #endif
661 _physicalStateObj.Dispose();
663 else {
664 // Non-MARS, and session is ok - remove its owner
665 _physicalStateObj.Owner = null;
669 // This is called from a ThreadAbort - ensure that it can be run from a CER Catch
670 internal void BestEffortCleanup() {
671 _state = TdsParserState.Broken;
673 var stateObj = _physicalStateObj;
674 if (stateObj != null) {
675 var stateObjHandle = stateObj.Handle;
676 if (stateObjHandle != null) {
677 stateObjHandle.Dispose();
681 if (_fMARS) {
682 var sessionPool = _sessionPool;
683 if (sessionPool != null) {
684 sessionPool.BestEffortCleanup();
687 var marsStateObj = _pMarsPhysicalConObj;
688 if (marsStateObj != null) {
689 var marsStateObjHandle = marsStateObj.Handle;
690 if (marsStateObjHandle != null) {
691 marsStateObjHandle.Dispose();
697 private void SendPreLoginHandshake(byte[] instanceName, bool encrypt) {
698 // PreLoginHandshake buffer consists of:
699 // 1) Standard header, with type = MT_PRELOGIN
700 // 2) Consecutive 5 bytes for each option, (1 byte length, 2 byte offset, 2 byte payload length)
701 // 3) Consecutive data blocks for each option
703 // NOTE: packet data needs to be big endian - not the standard little endian used by
704 // the rest of the parser.
706 _physicalStateObj._outputMessageType = TdsEnums.MT_PRELOGIN;
708 // Initialize option offset into payload buffer
709 // 5 bytes for each option (1 byte length, 2 byte offset, 2 byte payload length)
710 int offset = (int)PreLoginOptions.NUMOPT * 5 + 1;
712 byte[] payload = new byte[(int)PreLoginOptions.NUMOPT * 5 + TdsEnums.MAX_PRELOGIN_PAYLOAD_LENGTH];
713 int payloadLength = 0;
718 for (int option = (int)PreLoginOptions.VERSION; option < (int)PreLoginOptions.NUMOPT; option++) {
719 int optionDataSize = 0;
721 // Fill in the option
722 _physicalStateObj.WriteByte((byte)option);
724 // Fill in the offset of the option data
725 _physicalStateObj.WriteByte((byte)((offset & 0xff00) >> 8)); // send upper order byte
726 _physicalStateObj.WriteByte((byte)(offset & 0x00ff)); // send lower order byte
728 switch (option) {
729 case (int)PreLoginOptions.VERSION:
730 Version systemDataVersion = ADP.GetAssemblyVersion();
732 // Major and minor
733 payload[payloadLength++] = (byte)(systemDataVersion.Major & 0xff);
734 payload[payloadLength++] = (byte)(systemDataVersion.Minor & 0xff);
736 // Build (Big Endian)
737 payload[payloadLength++] = (byte)((systemDataVersion.Build & 0xff00) >> 8);
738 payload[payloadLength++] = (byte)(systemDataVersion.Build & 0xff);
740 // Sub-build (Little Endian)
741 payload[payloadLength++] = (byte)(systemDataVersion.Revision & 0xff);
742 payload[payloadLength++] = (byte)((systemDataVersion.Revision & 0xff00) >> 8);
743 offset += 6;
744 optionDataSize = 6;
745 break;
747 case (int)PreLoginOptions.ENCRYPT:
748 if (_encryptionOption == EncryptionOptions.NOT_SUP) {
749 // If OS doesn't support encryption, inform server not supported.
750 payload[payloadLength] = (byte)EncryptionOptions.NOT_SUP;
752 else {
753 // Else, inform server of user request.
754 if (encrypt) {
755 payload[payloadLength] = (byte)EncryptionOptions.ON;
756 _encryptionOption = EncryptionOptions.ON;
758 else {
759 payload[payloadLength] = (byte)EncryptionOptions.OFF;
760 _encryptionOption = EncryptionOptions.OFF;
764 payloadLength += 1;
765 offset += 1;
766 optionDataSize = 1;
767 break;
769 case (int)PreLoginOptions.INSTANCE:
770 int i = 0;
772 while (instanceName[i] != 0) {
773 payload[payloadLength] = instanceName[i];
774 payloadLength++;
775 i++;
778 payload[payloadLength] = 0; // null terminate
779 payloadLength++;
780 i++;
782 offset += i;
783 optionDataSize = i;
784 break;
786 case (int)PreLoginOptions.THREADID:
787 Int32 threadID = TdsParserStaticMethods.GetCurrentThreadIdForTdsLoginOnly();
789 payload[payloadLength++] = (byte)((0xff000000 & threadID) >> 24);
790 payload[payloadLength++] = (byte)((0x00ff0000 & threadID) >> 16);
791 payload[payloadLength++] = (byte)((0x0000ff00 & threadID) >> 8);
792 payload[payloadLength++] = (byte)(0x000000ff & threadID);
793 offset += 4;
794 optionDataSize = 4;
795 break;
797 case (int)PreLoginOptions.MARS:
798 payload[payloadLength++] = (byte)(_fMARS ? 1 : 0);
799 offset += 1;
800 optionDataSize += 1;
801 break;
803 case (int)PreLoginOptions.TRACEID:
804 byte[] connectionIdBytes = _connHandler._clientConnectionId.ToByteArray();
805 Debug.Assert(GUID_SIZE == connectionIdBytes.Length);
806 Buffer.BlockCopy(connectionIdBytes, 0, payload, payloadLength, GUID_SIZE);
807 payloadLength += GUID_SIZE;
808 offset += GUID_SIZE;
809 optionDataSize = GUID_SIZE;
811 ActivityCorrelator.ActivityId actId = ActivityCorrelator.Next();
812 connectionIdBytes = actId.Id.ToByteArray();
813 Buffer.BlockCopy(connectionIdBytes, 0, payload, payloadLength, GUID_SIZE);
814 payloadLength += GUID_SIZE;
815 payload[payloadLength++] = (byte)(0x000000ff & actId.Sequence);
816 payload[payloadLength++] = (byte)((0x0000ff00 & actId.Sequence) >> 8);
817 payload[payloadLength++] = (byte)((0x00ff0000 & actId.Sequence) >> 16);
818 payload[payloadLength++] = (byte)((0xff000000 & actId.Sequence) >> 24);
819 int actIdSize = GUID_SIZE + sizeof(UInt32);
820 offset += actIdSize;
821 optionDataSize += actIdSize;
822 Bid.Trace("<sc.TdsParser.SendPreLoginHandshake|INFO> ClientConnectionID %ls, ActivityID %ls\n", _connHandler._clientConnectionId.ToString(), actId.ToString());
823 break;
825 case (int)PreLoginOptions.FEDAUTHREQUIRED:
826 payload[payloadLength++] = 0x01;
827 offset += 1;
828 optionDataSize += 1;
829 break;
831 default:
832 Debug.Assert(false, "UNKNOWN option in SendPreLoginHandshake");
833 break;
836 // Write data length
837 _physicalStateObj.WriteByte((byte)((optionDataSize & 0xff00) >> 8));
838 _physicalStateObj.WriteByte((byte)(optionDataSize & 0x00ff));
841 // Write out last option - to let server know the second part of packet completed
842 _physicalStateObj.WriteByte((byte)PreLoginOptions.LASTOPT);
844 // Write out payload
845 _physicalStateObj.WriteByteArray(payload, payloadLength, 0);
847 // Flush packet
848 _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
851 private PreLoginHandshakeStatus ConsumePreLoginHandshake(SqlAuthenticationMethod authType, bool encrypt, bool trustServerCert, bool integratedSecurity, out bool marsCapable, out bool fedAuthRequired) {
853 // Assign default values
854 marsCapable = _fMARS;
855 fedAuthRequired = false;
857 bool isYukonOrLater = false;
861 Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
862 bool result = _physicalStateObj.TryReadNetworkPacket();
863 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
865 if (_physicalStateObj._inBytesRead == 0) {
866 // If the server did not respond then something has gone wrong and we need to close the connection
867 _physicalStateObj.AddError(new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.PreloginError(), "", 0));
868 _physicalStateObj.Dispose();
869 ThrowExceptionAndWarning(_physicalStateObj);
872 // SEC
873 byte[] payload = new byte[_physicalStateObj._inBytesRead - _physicalStateObj._inBytesUsed - _physicalStateObj._inputHeaderLen];
875 Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
876 result = _physicalStateObj.TryReadByteArray(payload, 0, payload.Length);
877 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
879 if (payload[0] == 0xaa) {
880 // If the first byte is 0xAA, we are connecting to a 6.5 or earlier server, which
881 // is not supported. SQL BU DT 296425
882 throw SQL.InvalidSQLServerVersionUnknown();
885 int offset = 0;
886 int payloadOffset = 0;
887 int payloadLength = 0;
888 int option = payload[offset++];
890 while (option != (byte)PreLoginOptions.LASTOPT) {
891 switch (option) {
892 case (int)PreLoginOptions.VERSION:
893 payloadOffset = payload[offset++] << 8 | payload[offset++];
894 payloadLength = payload[offset++] << 8 | payload[offset++];
896 byte majorVersion = payload[payloadOffset];
897 byte minorVersion = payload[payloadOffset + 1];
898 int level = (payload[payloadOffset + 2] << 8) |
899 payload[payloadOffset + 3];
901 isYukonOrLater = majorVersion >= 9;
902 if (!isYukonOrLater) {
903 marsCapable = false; // If pre-Yukon, MARS not supported.
906 break;
908 case (int)PreLoginOptions.ENCRYPT:
909 payloadOffset = payload[offset++] << 8 | payload[offset++];
910 payloadLength = payload[offset++] << 8 | payload[offset++];
912 EncryptionOptions serverOption = (EncryptionOptions)payload[payloadOffset];
914 /* internal enum EncryptionOptions {
915 OFF,
917 NOT_SUP,
918 REQ,
919 LOGIN
920 } */
922 switch (_encryptionOption) {
923 case (EncryptionOptions.ON):
924 if (serverOption == EncryptionOptions.NOT_SUP) {
925 _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0));
926 _physicalStateObj.Dispose();
927 ThrowExceptionAndWarning(_physicalStateObj);
930 break;
932 case (EncryptionOptions.OFF):
933 if (serverOption == EncryptionOptions.OFF) {
934 // Only encrypt login.
935 _encryptionOption = EncryptionOptions.LOGIN;
937 else if (serverOption == EncryptionOptions.REQ) {
938 // Encrypt all.
939 _encryptionOption = EncryptionOptions.ON;
942 break;
944 case (EncryptionOptions.NOT_SUP):
945 if (serverOption == EncryptionOptions.REQ) {
946 _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0));
947 _physicalStateObj.Dispose();
948 ThrowExceptionAndWarning(_physicalStateObj);
951 break;
953 default:
954 Debug.Assert(false, "Invalid client encryption option detected");
955 break;
958 if (_encryptionOption == EncryptionOptions.ON ||
959 _encryptionOption == EncryptionOptions.LOGIN) {
960 UInt32 error = 0;
962 // If we're using legacy server certificate validation behavior (Authentication keyword not provided and not using access token), then validate if
963 // Encrypt=true and Trust Sever Certificate = false.
964 // If using Authentication keyword or access token, validate if Trust Server Certificate=false.
965 bool shouldValidateServerCert = (encrypt && !trustServerCert) || ((authType != SqlAuthenticationMethod.NotSpecified || _connHandler._accessTokenInBytes != null) && !trustServerCert);
967 UInt32 info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0)
968 | (isYukonOrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0);
970 if (encrypt && !integratedSecurity) {
971 // optimization: in case of SQL Authentication and encryption, set SNI_SSL_IGNORE_CHANNEL_BINDINGS to let SNI
972 // know that it does not need to allocate/retrieve the Channel Bindings from the SSL context.
973 info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS;
976 // Add SSL (Encryption) SNI provider.
977 error = SNINativeMethodWrapper.SNIAddProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.SSL_PROV, ref info);
979 if (error != TdsEnums.SNI_SUCCESS) {
980 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
981 ThrowExceptionAndWarning(_physicalStateObj);
984 // in the case where an async connection is made, encryption is used and Windows Authentication is used,
985 // wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its
986 // Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
987 // before calling SNISecGenClientContext).
988 error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining());
989 if (error != TdsEnums.SNI_SUCCESS)
991 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
992 ThrowExceptionAndWarning(_physicalStateObj);
995 // create a new packet encryption changes the internal packet size Bug# 228403
996 try {} // EmptyTry/Finally to avoid FXCop violation
997 finally {
998 _physicalStateObj.ClearAllWritePackets();
1002 break;
1004 case (int)PreLoginOptions.INSTANCE:
1005 payloadOffset = payload[offset++] << 8 | payload[offset++];
1006 payloadLength = payload[offset++] << 8 | payload[offset++];
1008 byte ERROR_INST = 0x1;
1009 byte instanceResult = payload[payloadOffset];
1011 if (instanceResult == ERROR_INST) {
1012 // Check if server says ERROR_INST. That either means the cached info
1013 // we used to connect is not valid or we connected to a named instance
1014 // listening on default params.
1015 return PreLoginHandshakeStatus.InstanceFailure;
1018 break;
1020 case (int)PreLoginOptions.THREADID:
1021 // DO NOTHING FOR THREADID
1022 offset += 4;
1023 break;
1025 case (int)PreLoginOptions.MARS:
1026 payloadOffset = payload[offset++] << 8 | payload[offset++];
1027 payloadLength = payload[offset++] << 8 | payload[offset++];
1029 marsCapable = (payload[payloadOffset] == 0 ? false : true);
1031 Debug.Assert(payload[payloadOffset] == 0 || payload[payloadOffset] == 1, "Value for Mars PreLoginHandshake option not equal to 1 or 0!");
1032 break;
1034 case (int)PreLoginOptions.TRACEID:
1035 // DO NOTHING FOR TRACEID
1036 offset += 4;
1037 break;
1039 case (int)PreLoginOptions.FEDAUTHREQUIRED:
1040 payloadOffset = payload[offset++] << 8 | payload[offset++];
1041 payloadLength = payload[offset++] << 8 | payload[offset++];
1043 // Only 0x00 and 0x01 are accepted values from the server.
1044 if (payload[payloadOffset] != 0x00 && payload[payloadOffset] != 0x01) {
1045 Bid.Trace("<sc.TdsParser.ConsumePreLoginHandshake|ERR> %d#, Server sent an unexpected value for FedAuthRequired PreLogin Option. Value was %d.\n", ObjectID, (int)payload[payloadOffset]);
1046 throw SQL.ParsingErrorValue(ParsingErrorState.FedAuthRequiredPreLoginResponseInvalidValue, (int)payload[payloadOffset]);
1049 // We must NOT use the response for the FEDAUTHREQUIRED PreLogin option, if the connection string option
1050 // was not using the new Authentication keyword or in other words, if Authentication=NotSpecified
1051 // Or AccessToken is not null, mean token based authentication is used.
1052 if ((_connHandler.ConnectionOptions != null
1053 && _connHandler.ConnectionOptions.Authentication != SqlAuthenticationMethod.NotSpecified)
1054 || _connHandler._accessTokenInBytes != null)
1056 fedAuthRequired = payload[payloadOffset] == 0x01 ? true : false;
1058 break;
1060 default:
1061 Debug.Assert(false, "UNKNOWN option in ConsumePreLoginHandshake, option:" + option);
1063 // DO NOTHING FOR THESE UNKNOWN OPTIONS
1064 offset += 4;
1066 break;
1069 if (offset < payload.Length) {
1070 option = payload[offset++];
1072 else {
1073 break;
1077 return PreLoginHandshakeStatus.Successful;
1080 internal void Deactivate(bool connectionIsDoomed) {
1081 // Called when the connection that owns us is deactivated.
1083 if (Bid.AdvancedOn) {
1084 Bid.Trace("<sc.TdsParser.Deactivate|ADV> %d# deactivating\n", ObjectID);
1087 if (Bid.IsOn(Bid.ApiGroup.StateDump)) {
1088 Bid.Trace("<sc.TdsParser.Deactivate|STATE> %d#, %ls\n", ObjectID, TraceString());
1091 if (MARSOn) {
1092 _sessionPool.Deactivate();
1095 Debug.Assert(connectionIsDoomed || null == _pendingTransaction, "pending transaction at disconnect?");
1097 if (!connectionIsDoomed && null != _physicalStateObj) {
1098 if (_physicalStateObj._pendingData) {
1099 DrainData(_physicalStateObj);
1102 if (_physicalStateObj.HasOpenResult) { // SQL BU DT 383773 - need to decrement openResultCount for all pending operations.
1103 _physicalStateObj.DecrementOpenResultCount();
1107 // Any active, non-distributed transaction must be rolled back. We
1108 // need to wait for distributed transactions to be completed by the
1109 // transaction manager -- we don't want to automatically roll them
1110 // back.
1112 // Note that when there is a transaction delegated to this connection,
1113 // we will defer the deactivation of this connection until the
1114 // transaction manager completes the transaction.
1115 SqlInternalTransaction currentTransaction = CurrentTransaction;
1117 if (null != currentTransaction && currentTransaction.HasParentTransaction) {
1118 currentTransaction.CloseFromConnection();
1119 Debug.Assert(null == CurrentTransaction, "rollback didn't clear current transaction?");
1122 Statistics = null; // must come after CleanWire or we won't count the stuff that happens there...
1125 // Used to close the connection and then free the memory allocated for the netlib connection.
1126 internal void Disconnect() {
1127 if (null != _sessionPool) {
1128 // MARSOn may be true, but _sessionPool not yet created
1129 _sessionPool.Dispose();
1132 // Can close the connection if its open or broken
1133 if (_state != TdsParserState.Closed) {
1134 //benign assert - the user could close the connection before consuming all the data
1135 //Debug.Assert(_physicalStateObj._inBytesUsed == _physicalStateObj._inBytesRead && _physicalStateObj._outBytesUsed == _physicalStateObj._inputHeaderLen, "TDSParser closed with data not fully sent or consumed.");
1137 _state = TdsParserState.Closed;
1139 try {
1140 // If the _physicalStateObj has an owner, we will delay the disposal until the owner is finished with it
1141 if (!_physicalStateObj.HasOwner) {
1142 _physicalStateObj.SniContext = SniContext.Snix_Close;
1143 #if DEBUG
1144 _physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
1145 #endif
1146 _physicalStateObj.Dispose();
1148 else {
1149 // Remove the "initial" callback (this will allow the stateObj to be GC collected if need be)
1150 _physicalStateObj.DecrementPendingCallbacks(false);
1153 // Not allocated until MARS is actually enabled in SNI.
1154 if (null != _pMarsPhysicalConObj) {
1155 _pMarsPhysicalConObj.Dispose();
1158 finally {
1159 _pMarsPhysicalConObj = null;
1164 // Fires a single InfoMessageEvent
1165 private void FireInfoMessageEvent(SqlConnection connection, TdsParserStateObject stateObj, SqlError error) {
1167 string serverVersion = null;
1169 Debug.Assert(connection != null && _connHandler.Connection == connection);
1171 if (_state == TdsParserState.OpenLoggedIn) {
1172 serverVersion = _connHandler.ServerVersion;
1175 SqlErrorCollection sqlErs = new SqlErrorCollection();
1177 sqlErs.Add(error);
1179 SqlException exc = SqlException.CreateException(sqlErs, serverVersion, _connHandler);
1181 bool notified;
1182 connection.OnInfoMessage(new SqlInfoMessageEventArgs(exc), out notified);
1183 if (notified) {
1184 // observable side-effects, no retry
1185 stateObj._syncOverAsync = true;
1187 return;
1190 internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) {
1191 Debug.Assert(_currentTransaction != null && _currentTransaction == internalTransaction, "disconnecting different transaction");
1193 if (_currentTransaction != null && _currentTransaction == internalTransaction) {
1194 _currentTransaction = null;
1198 internal void RollbackOrphanedAPITransactions() {
1199 // Any active, non-distributed transaction must be rolled back.
1200 SqlInternalTransaction currentTransaction = CurrentTransaction;
1202 if (null != currentTransaction && currentTransaction.HasParentTransaction && currentTransaction.IsOrphaned) {
1203 currentTransaction.CloseFromConnection();
1204 Debug.Assert(null == CurrentTransaction, "rollback didn't clear current transaction?");
1208 internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, bool callerHasConnectionLock = false, bool asyncClose = false) {
1209 Debug.Assert(!callerHasConnectionLock || _connHandler._parserLock.ThreadMayHaveLock(), "Caller claims to have lock, but connection lock is not taken");
1211 SqlException exception = null;
1212 bool breakConnection;
1214 // This function should only be called when there was an error or warning. If there aren't any
1215 // errors, the handler will be called for the warning(s). If there was an error, the warning(s) will
1216 // be copied to the end of the error collection so that the user may see all the errors and also the
1217 // warnings that occurred.
1218 // can be deleted)
1219 SqlErrorCollection temp = stateObj.GetFullErrorAndWarningCollection(out breakConnection);
1221 Debug.Assert(temp.Count > 0, "TdsParser::ThrowExceptionAndWarning called with no exceptions or warnings!");
1222 if (temp.Count == 0)
1224 Bid.Trace("<sc.TdsParser.ThrowExceptionAndWarning|ERR> Potential multi-threaded misuse of connection, unexpectedly empty warnings/errors under lock %d#\n", ObjectID);
1226 Debug.Assert(_connHandler != null, "TdsParser::ThrowExceptionAndWarning called with null connectionHandler!");
1228 // Don't break the connection if it is already closed
1229 breakConnection &= (TdsParserState.Closed != _state);
1230 if (breakConnection) {
1231 if ((_state == TdsParserState.OpenNotLoggedIn) && (_connHandler.ConnectionOptions.TransparentNetworkIPResolution || _connHandler.ConnectionOptions.MultiSubnetFailover || _loginWithFailover) && (temp.Count == 1) && ((temp[0].Number == TdsEnums.TIMEOUT_EXPIRED) || (temp[0].Number == TdsEnums.SNI_WAIT_TIMEOUT))) {
1232 // DevDiv2 Bug 459546: With "MultiSubnetFailover=yes" in the Connection String, SQLClient incorrectly throws a Timeout using shorter time slice (3-4 seconds), not honoring the actual 'Connect Timeout'
1233 // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/459546
1234 // For Multisubnet Failover we slice the timeout to make reconnecting faster (with the assumption that the server will not failover instantaneously)
1235 // However, when timeout occurs we need to not doom the internal connection and also to mark the TdsParser as closed such that the login will be will retried
1236 breakConnection = false;
1237 Disconnect();
1239 else {
1240 _state = TdsParserState.Broken;
1244 Debug.Assert(temp != null, "TdsParser::ThrowExceptionAndWarning: 0 errors in collection");
1245 if (temp != null && temp.Count > 0) {
1246 // Construct the exception now that we've collected all the errors
1247 string serverVersion = null;
1248 if (_state == TdsParserState.OpenLoggedIn) {
1249 serverVersion = _connHandler.ServerVersion;
1251 exception = SqlException.CreateException(temp, serverVersion, _connHandler);
1252 if (exception.Procedure == TdsEnums.INIT_ADAL_PACKAGE || exception.Procedure == TdsEnums.INIT_SSPI_PACKAGE) {
1253 exception._doNotReconnect = true;
1257 // call OnError outside of _ErrorCollectionLock to avoid deadlock
1258 if (exception != null) {
1259 if (breakConnection) {
1260 // report exception to pending async operation
1261 // before OnConnectionClosed overrides the exception
1262 // due to connection close notification through references
1263 var taskSource = stateObj._networkPacketTaskSource;
1264 if (taskSource != null) {
1265 taskSource.TrySetException(ADP.ExceptionWithStackTrace(exception));
1269 if (asyncClose) {
1270 // Wait until we have the parser lock, then try to close
1271 var connHandler = _connHandler;
1272 Action<Action> wrapCloseAction = closeAction => {
1273 Task.Factory.StartNew(() => {
1274 connHandler._parserLock.Wait(canReleaseFromAnyThread: false);
1275 connHandler.ThreadHasParserLockForClose = true;
1276 try {
1277 closeAction();
1279 finally {
1280 connHandler.ThreadHasParserLockForClose = false;
1281 connHandler._parserLock.Release();
1286 _connHandler.OnError(exception, breakConnection, wrapCloseAction);
1288 else {
1289 // Let close know that we already have the _parserLock
1290 bool threadAlreadyHadParserLockForClose = _connHandler.ThreadHasParserLockForClose;
1291 if (callerHasConnectionLock) {
1292 _connHandler.ThreadHasParserLockForClose = true;
1294 try {
1295 // the following handler will throw an exception or generate a warning event
1296 _connHandler.OnError(exception, breakConnection);
1298 finally {
1299 if (callerHasConnectionLock) {
1300 _connHandler.ThreadHasParserLockForClose = threadAlreadyHadParserLockForClose;
1307 internal SqlError ProcessSNIError(TdsParserStateObject stateObj) {
1308 #if DEBUG
1309 // There is an exception here for MARS as its possible that another thread has closed the connection just as we see an error
1310 Debug.Assert(SniContext.Undefined!=stateObj.DebugOnlyCopyOfSniContext || ((_fMARS) && ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken))), "SniContext must not be None");
1311 #endif
1312 SNINativeMethodWrapper.SNI_Error sniError = new SNINativeMethodWrapper.SNI_Error();
1313 SNINativeMethodWrapper.SNIGetLastError(sniError);
1315 if (sniError.sniError != 0) {
1317 // handle special SNI error codes that are converted into exception which is not a SqlException.
1318 switch (sniError.sniError) {
1319 case (int)SNINativeMethodWrapper.SniSpecialErrors.MultiSubnetFailoverWithMoreThan64IPs:
1320 // Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported.
1321 throw SQL.MultiSubnetFailoverWithMoreThan64IPs();
1323 case (int)SNINativeMethodWrapper.SniSpecialErrors.MultiSubnetFailoverWithInstanceSpecified:
1324 // Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported.
1325 throw SQL.MultiSubnetFailoverWithInstanceSpecified();
1327 case (int)SNINativeMethodWrapper.SniSpecialErrors.MultiSubnetFailoverWithNonTcpProtocol:
1328 // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol.
1329 throw SQL.MultiSubnetFailoverWithNonTcpProtocol();
1331 // continue building SqlError instance
1335 // error.errorMessage is null terminated with garbage beyond that, since fixed length
1336 string errorMessage;
1337 int MessageLength = Array.IndexOf(sniError.errorMessage, '\0');
1338 if (MessageLength == -1) {
1339 errorMessage = String.Empty; // If we don't see the expected null return nothing.
1340 } else {
1341 errorMessage = new String(sniError.errorMessage, 0, MessageLength);
1344 // Format SNI errors and add Context Information
1346 // General syntax is:
1347 // <sqlclient message>
1348 // (provider:<SNIx provider>, error: <SNIx error code> - <SNIx error message>)
1350 // errorMessage | sniError |
1351 // -------------------------------------------
1352 // ==null | x | must never happen
1353 // !=null | != 0 | retrieve corresponding errorMessage from resources
1354 // !=null | == 0 | replace text left of errorMessage
1357 Debug.Assert(!ADP.IsEmpty(errorMessage),"Empty error message received from SNI");
1359 string sqlContextInfo = Res.GetString(Enum.GetName(typeof(SniContext), stateObj.SniContext));
1361 string providerRid = String.Format((IFormatProvider)null,"SNI_PN{0}", (int)sniError.provider);
1362 string providerName = Res.GetString(providerRid);
1363 Debug.Assert(!ADP.IsEmpty(providerName), String.Format((IFormatProvider)null,"invalid providerResourceId '{0}'", providerRid));
1364 uint win32ErrorCode = sniError.nativeError;
1366 if (sniError.sniError == 0) {
1367 // Provider error. The message from provider is preceeded with non-localizable info from SNI
1368 // strip provider info from SNI
1370 int iColon = errorMessage.IndexOf(':');
1371 Debug.Assert(0<=iColon, "':' character missing in sni errorMessage");
1372 Debug.Assert(errorMessage.Length>iColon+1 && errorMessage[iColon+1]==' ', "Expecting a space after the ':' character");
1374 // extract the message excluding the colon and trailing cr/lf chars
1375 if (0<=iColon) {
1376 int len = errorMessage.Length;
1377 len -=2; // exclude "\r\n" sequence
1378 iColon+=2; // skip over ": " sequence
1379 len-=iColon;
1381 The error message should come back in the following format: "TCP Provider: MESSAGE TEXT"
1382 Fix Bug 370686, if the message is recieved on a Win9x OS, the error message will not contain MESSAGE TEXT
1383 per Bug: 269574. If we get a errormessage with no message text, just return the entire message otherwise
1384 return just the message text.
1386 if (len > 0) {
1387 errorMessage = errorMessage.Substring(iColon, len);
1391 else {
1392 // SNI error. Replace the entire message
1394 errorMessage = SQL.GetSNIErrorMessage((int)sniError.sniError);
1396 // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
1397 if (sniError.sniError == (int)SNINativeMethodWrapper.SniSpecialErrors.LocalDBErrorCode) {
1398 errorMessage += LocalDBAPI.GetLocalDBMessage((int)sniError.nativeError);
1399 win32ErrorCode = 0;
1402 errorMessage = String.Format((IFormatProvider)null, "{0} (provider: {1}, error: {2} - {3})",
1403 sqlContextInfo, providerName, (int)sniError.sniError, errorMessage);
1405 return new SqlError((int)sniError.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS,
1406 _server, errorMessage, sniError.function, (int)sniError.lineNumber, win32ErrorCode);
1409 internal void CheckResetConnection(TdsParserStateObject stateObj) {
1410 if (_fResetConnection && !stateObj._fResetConnectionSent) {
1411 Debug.Assert(stateObj._outputPacketNumber == 1 || stateObj._outputPacketNumber == 2, "In ResetConnection logic unexpectedly!");
1412 try {
1413 if (_fMARS && !stateObj._fResetEventOwned) {
1414 // If using Async & MARS and we do not own ResetEvent - grab it. We need to not grab lock here
1415 // for case where multiple packets are sent to server from one execute.
1416 stateObj._fResetEventOwned = _resetConnectionEvent.WaitOne(stateObj.GetTimeoutRemaining(), false);
1418 if (stateObj._fResetEventOwned) {
1419 if (stateObj.TimeoutHasExpired) {
1420 // We didn't timeout on the WaitOne, but we timed out by the time we decremented stateObj._timeRemaining.
1421 stateObj._fResetEventOwned = !_resetConnectionEvent.Set();
1422 stateObj.TimeoutTime = 0;
1426 if (!stateObj._fResetEventOwned) {
1427 // We timed out waiting for ResetEvent. Throw timeout exception and reset
1428 // the buffer. Nothing else to do since we did not actually send anything
1429 // to the server.
1430 stateObj.ResetBuffer();
1431 Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point.");
1432 stateObj.AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, (byte)0x00, TdsEnums.MIN_ERROR_CLASS, _server, _connHandler.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT));
1433 Debug.Assert(_connHandler._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock");
1434 ThrowExceptionAndWarning(stateObj, callerHasConnectionLock: true);
1438 if (_fResetConnection) {
1439 // Check again to see if we need to send reset.
1441 Debug.Assert(!stateObj._fResetConnectionSent, "Unexpected state for sending reset connection");
1442 Debug.Assert(_isShiloh, "TdsParser.cs: Error! _fResetConnection true when not going against Shiloh or later!");
1444 if (_fPreserveTransaction) {
1445 // if we are reseting, set bit in header by or'ing with other value
1446 stateObj._outBuff[1] = (Byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION_PRESERVE_TRANSACTION);
1448 else {
1449 // if we are reseting, set bit in header by or'ing with other value
1450 stateObj._outBuff[1] = (Byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION);
1453 if (!_fMARS) {
1454 _fResetConnection = false; // If not MARS, can turn off flag now.
1455 _fPreserveTransaction = false;
1457 else {
1458 stateObj._fResetConnectionSent = true; // Otherwise set flag so we don't resend on multiple packet execute.
1461 else if (_fMARS && stateObj._fResetEventOwned) {
1462 Debug.Assert(!stateObj._fResetConnectionSent, "Unexpected state on WritePacket ResetConnection");
1464 // Otherwise if Yukon and we grabbed the event, free it. Another execute grabbed the event and
1465 // took care of sending the reset.
1466 stateObj._fResetEventOwned = !_resetConnectionEvent.Set();
1467 Debug.Assert(!stateObj._fResetEventOwned, "Invalid AutoResetEvent state!");
1470 catch (Exception) {
1471 if (_fMARS && stateObj._fResetEventOwned) {
1472 // If exception thrown, and we are on Yukon and own the event, release it!
1473 stateObj._fResetConnectionSent = false;
1474 stateObj._fResetEventOwned = !_resetConnectionEvent.Set();
1475 Debug.Assert(!stateObj._fResetEventOwned, "Invalid AutoResetEvent state!");
1478 throw;
1481 #if DEBUG
1482 else {
1483 Debug.Assert(!_fResetConnection ||
1484 (_fResetConnection && stateObj._fResetConnectionSent && stateObj._fResetEventOwned),
1485 "Unexpected state on else ResetConnection block in WritePacket");
1487 #endif
1491 // Takes a 16 bit short and writes it.
1493 internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) {
1494 if (null == stateObj._bShortBytes) {
1495 stateObj._bShortBytes = new byte[2];
1497 else {
1498 Debug.Assert(2 == stateObj._bShortBytes.Length);
1501 byte[] bytes = stateObj._bShortBytes;
1502 int current = 0;
1503 bytes[current++] = (byte)(v & 0xff);
1504 bytes[current++] = (byte)((v >> 8) & 0xff);
1505 return bytes;
1508 internal void WriteShort(int v, TdsParserStateObject stateObj) {
1509 ReliabilitySection.Assert("unreliable call to WriteShort"); // you need to setup for a thread abort somewhere before you call this method
1511 if ((stateObj._outBytesUsed + 2) > stateObj._outBuff.Length) {
1512 // if all of the short doesn't fit into the buffer
1513 stateObj.WriteByte((byte)(v & 0xff));
1514 stateObj.WriteByte((byte)((v >> 8) & 0xff));
1516 else {
1517 // all of the short fits into the buffer
1518 stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff);
1519 stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff);
1520 stateObj._outBytesUsed += 2;
1524 internal void WriteUnsignedShort(ushort us, TdsParserStateObject stateObj) {
1525 WriteShort((short)us, stateObj);
1529 // Takes a long and writes out an unsigned int
1531 internal byte[] SerializeUnsignedInt(uint i, TdsParserStateObject stateObj) {
1532 return SerializeInt((int)i, stateObj);
1535 internal void WriteUnsignedInt(uint i, TdsParserStateObject stateObj) {
1536 WriteInt((int)i, stateObj);
1540 // Takes an int and writes it as an int.
1542 internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) {
1543 if (null == stateObj._bIntBytes) {
1544 stateObj._bIntBytes = new byte[4];
1546 else {
1547 Debug.Assert (4 == stateObj._bIntBytes.Length);
1550 int current = 0;
1551 byte[] bytes = stateObj._bIntBytes;
1552 bytes[current++] = (byte)(v & 0xff);
1553 bytes[current++] = (byte)((v >> 8) & 0xff);
1554 bytes[current++] = (byte)((v >> 16) & 0xff);
1555 bytes[current++] = (byte)((v >> 24) & 0xff);
1556 return bytes;
1559 internal void WriteInt(int v, TdsParserStateObject stateObj) {
1560 ReliabilitySection.Assert("unreliable call to WriteInt"); // you need to setup for a thread abort somewhere before you call this method
1562 if ((stateObj._outBytesUsed + 4) > stateObj._outBuff.Length) {
1563 // if all of the int doesn't fit into the buffer
1564 for (int shiftValue = 0; shiftValue < sizeof(int) * 8; shiftValue += 8) {
1565 stateObj.WriteByte((byte)((v >> shiftValue) & 0xff));
1568 else {
1569 // all of the int fits into the buffer
1570 // NOTE: We don't use a loop here for performance
1571 stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff);
1572 stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff);
1573 stateObj._outBuff[stateObj._outBytesUsed + 2] = (byte)((v >> 16) & 0xff);
1574 stateObj._outBuff[stateObj._outBytesUsed + 3] = (byte)((v >> 24) & 0xff);
1575 stateObj._outBytesUsed += 4;
1580 // Takes a float and writes it as a 32 bit float.
1582 internal byte[] SerializeFloat(float v) {
1583 if (Single.IsInfinity(v) || Single.IsNaN(v)) {
1584 throw ADP.ParameterValueOutOfRange(v.ToString());
1587 return BitConverter.GetBytes(v);
1590 internal void WriteFloat(float v, TdsParserStateObject stateObj) {
1591 byte[] bytes = BitConverter.GetBytes(v);
1593 stateObj.WriteByteArray(bytes, bytes.Length, 0);
1597 // Takes a long and writes it as a long.
1599 internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) {
1600 int current = 0;
1601 if (null == stateObj._bLongBytes) {
1602 stateObj._bLongBytes = new byte[8];
1605 byte[] bytes = stateObj._bLongBytes;
1606 Debug.Assert (8 == bytes.Length, "Cached buffer has wrong size");
1608 bytes[current++] = (byte)(v & 0xff);
1609 bytes[current++] = (byte)((v >> 8) & 0xff);
1610 bytes[current++] = (byte)((v >> 16) & 0xff);
1611 bytes[current++] = (byte)((v >> 24) & 0xff);
1612 bytes[current++] = (byte)((v >> 32) & 0xff);
1613 bytes[current++] = (byte)((v >> 40) & 0xff);
1614 bytes[current++] = (byte)((v >> 48) & 0xff);
1615 bytes[current++] = (byte)((v >> 56) & 0xff);
1617 return bytes;
1620 internal void WriteLong(long v, TdsParserStateObject stateObj) {
1621 ReliabilitySection.Assert("unreliable call to WriteLong"); // you need to setup for a thread abort somewhere before you call this method
1623 if ((stateObj._outBytesUsed + 8) > stateObj._outBuff.Length) {
1624 // if all of the long doesn't fit into the buffer
1625 for (int shiftValue = 0; shiftValue < sizeof(long) * 8; shiftValue += 8) {
1626 stateObj.WriteByte((byte)((v >> shiftValue) & 0xff));
1629 else {
1630 // all of the long fits into the buffer
1631 // NOTE: We don't use a loop here for performance
1632 stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff);
1633 stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff);
1634 stateObj._outBuff[stateObj._outBytesUsed + 2] = (byte)((v >> 16) & 0xff);
1635 stateObj._outBuff[stateObj._outBytesUsed + 3] = (byte)((v >> 24) & 0xff);
1636 stateObj._outBuff[stateObj._outBytesUsed + 4] = (byte)((v >> 32) & 0xff);
1637 stateObj._outBuff[stateObj._outBytesUsed + 5] = (byte)((v >> 40) & 0xff);
1638 stateObj._outBuff[stateObj._outBytesUsed + 6] = (byte)((v >> 48) & 0xff);
1639 stateObj._outBuff[stateObj._outBytesUsed + 7] = (byte)((v >> 56) & 0xff);
1640 stateObj._outBytesUsed += 8;
1645 // Takes a long and writes part of it
1647 internal byte[] SerializePartialLong(long v, int length) {
1648 Debug.Assert(length <= 8, "Length specified is longer than the size of a long");
1649 Debug.Assert(length >= 0, "Length should not be negative");
1651 byte[] bytes = new byte[length];
1653 // all of the long fits into the buffer
1654 for (int index = 0; index < length; index++) {
1655 bytes[index] = (byte)((v >> (index * 8)) & 0xff);
1658 return bytes;
1661 internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj) {
1662 ReliabilitySection.Assert("unreliable call to WritePartialLong"); // you need to setup for a thread abort somewhere before you call this method
1663 Debug.Assert(length <= 8, "Length specified is longer than the size of a long");
1664 Debug.Assert(length >= 0, "Length should not be negative");
1666 if ((stateObj._outBytesUsed + length) > stateObj._outBuff.Length) {
1667 // if all of the long doesn't fit into the buffer
1668 for (int shiftValue = 0; shiftValue < length * 8; shiftValue += 8) {
1669 stateObj.WriteByte((byte)((v >> shiftValue) & 0xff));
1672 else {
1673 // all of the long fits into the buffer
1674 for (int index = 0; index < length; index++) {
1675 stateObj._outBuff[stateObj._outBytesUsed + index] = (byte)((v >> (index * 8)) & 0xff);
1677 stateObj._outBytesUsed += length;
1682 // Takes a ulong and writes it as a ulong.
1684 internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) {
1685 WriteLong((long)uv, stateObj);
1689 // Takes a double and writes it as a 64 bit double.
1691 internal byte[] SerializeDouble(double v) {
1692 if (Double.IsInfinity(v) || Double.IsNaN(v)) {
1693 throw ADP.ParameterValueOutOfRange(v.ToString());
1696 return BitConverter.GetBytes(v);
1699 internal void WriteDouble(double v, TdsParserStateObject stateObj) {
1700 byte[] bytes = BitConverter.GetBytes(v);
1702 stateObj.WriteByteArray(bytes, bytes.Length, 0);
1705 internal void PrepareResetConnection(bool preserveTransaction) {
1706 // Set flag to reset connection upon next use - only for use on shiloh!
1707 _fResetConnection = true;
1708 _fPreserveTransaction = preserveTransaction;
1711 internal bool RunReliably(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) {
1712 RuntimeHelpers.PrepareConstrainedRegions();
1713 try {
1714 #if DEBUG
1715 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
1716 RuntimeHelpers.PrepareConstrainedRegions();
1717 try {
1718 tdsReliabilitySection.Start();
1719 #endif //DEBUG
1720 return Run(runBehavior, cmdHandler, dataStream, bulkCopyHandler, stateObj);
1721 #if DEBUG
1723 finally {
1724 tdsReliabilitySection.Stop();
1726 #endif //DEBUG
1728 catch (OutOfMemoryException) {
1729 _connHandler.DoomThisConnection();
1730 throw;
1732 catch (StackOverflowException) {
1733 _connHandler.DoomThisConnection();
1734 throw;
1736 catch (ThreadAbortException) {
1737 _connHandler.DoomThisConnection();
1738 throw;
1743 internal bool Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) {
1744 bool syncOverAsync = stateObj._syncOverAsync;
1747 stateObj._syncOverAsync = true;
1749 bool dataReady;
1750 bool result = TryRun(runBehavior, cmdHandler, dataStream, bulkCopyHandler, stateObj, out dataReady);
1751 Debug.Assert(result == true, "Should never return false when _syncOverAsync is set");
1752 return dataReady;
1754 finally
1756 stateObj._syncOverAsync = syncOverAsync;
1760 /// <summary>
1761 /// Checks if the given token is a valid TDS token
1762 /// </summary>
1763 /// <param name="token">Token to check</param>
1764 /// <returns>True if the token is a valid TDS token, otherwise false</returns>
1765 internal static bool IsValidTdsToken(byte token) {
1766 return (
1767 token == TdsEnums.SQLERROR ||
1768 token == TdsEnums.SQLINFO ||
1769 token == TdsEnums.SQLLOGINACK ||
1770 token == TdsEnums.SQLENVCHANGE ||
1771 token == TdsEnums.SQLRETURNVALUE ||
1772 token == TdsEnums.SQLRETURNSTATUS ||
1773 token == TdsEnums.SQLCOLNAME ||
1774 token == TdsEnums.SQLCOLFMT ||
1775 token == TdsEnums.SQLCOLMETADATA ||
1776 token == TdsEnums.SQLALTMETADATA ||
1777 token == TdsEnums.SQLTABNAME ||
1778 token == TdsEnums.SQLCOLINFO ||
1779 token == TdsEnums.SQLORDER ||
1780 token == TdsEnums.SQLALTROW ||
1781 token == TdsEnums.SQLROW ||
1782 token == TdsEnums.SQLNBCROW ||
1783 token == TdsEnums.SQLDONE ||
1784 token == TdsEnums.SQLDONEPROC ||
1785 token == TdsEnums.SQLDONEINPROC ||
1786 token == TdsEnums.SQLROWCRC ||
1787 token == TdsEnums.SQLSECLEVEL ||
1788 token == TdsEnums.SQLPROCID ||
1789 token == TdsEnums.SQLOFFSET ||
1790 token == TdsEnums.SQLSSPI ||
1791 token == TdsEnums.SQLFEATUREEXTACK ||
1792 token == TdsEnums.SQLSESSIONSTATE ||
1793 token == TdsEnums.SQLFEDAUTHINFO);
1796 // Main parse loop for the top-level tds tokens, calls back into the I*Handler interfaces
1797 internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, out bool dataReady) {
1798 ReliabilitySection.Assert("unreliable call to Run"); // you need to setup for a thread abort somewhere before you call this method
1799 Debug.Assert((SniContext.Undefined != stateObj.SniContext) && // SniContext must not be Undefined
1800 ((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)
1801 String.Format("Unexpected SniContext on call to TryRun; SniContext={0}", stateObj.SniContext));
1803 if (TdsParserState.Broken == State || TdsParserState.Closed == State ) {
1804 dataReady = true;
1805 return true; // Just in case this is called in a loop, expecting data to be returned.
1808 dataReady = false;
1810 do {
1811 // If there is data ready, but we didn't exit the loop, then something is wrong
1812 Debug.Assert(!dataReady, "dataReady not expected - did we forget to skip the row?");
1814 if (stateObj._internalTimeout) {
1815 runBehavior = RunBehavior.Attention;
1818 if (TdsParserState.Broken == State || TdsParserState.Closed == State)
1819 break; // jump out of the loop if the state is already broken or closed.
1821 if (!stateObj._accumulateInfoEvents && (stateObj._pendingInfoEvents != null)) {
1822 if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) {
1823 SqlConnection connection = null;
1824 if (_connHandler != null)
1825 connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref
1826 // We are omitting checks for error.Class in the code below (see processing of INFO) since we know (and assert) that error class
1827 // error.Class < TdsEnums.MIN_ERROR_CLASS for info message.
1828 // Also we know that TdsEnums.MIN_ERROR_CLASS<TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS
1829 if ((connection != null) && connection.FireInfoMessageEventOnUserErrors)
1831 foreach (SqlError error in stateObj._pendingInfoEvents)
1832 FireInfoMessageEvent(connection, stateObj, error);
1834 else
1835 foreach (SqlError error in stateObj._pendingInfoEvents)
1836 stateObj.AddWarning(error);
1839 stateObj._pendingInfoEvents=null;
1842 byte token;
1843 if (!stateObj.TryReadByte(out token)) {
1844 return false;
1847 if (!IsValidTdsToken(token)) {
1848 Debug.Assert(false, String.Format((IFormatProvider)null, "unexpected token; token = {0,-2:X2}", token));
1849 _state = TdsParserState.Broken;
1850 _connHandler.BreakConnection();
1851 Bid.Trace("<sc.TdsParser.Run|ERR> Potential multi-threaded misuse of connection, unexpected TDS token found %d#\n", ObjectID);
1852 throw SQL.ParsingErrorToken(ParsingErrorState.InvalidTdsTokenReceived, token); // MDAC 82443
1855 int tokenLength;
1856 if (!TryGetTokenLength(token, stateObj, out tokenLength)) {
1857 return false;
1860 switch (token) {
1861 case TdsEnums.SQLERROR:
1862 case TdsEnums.SQLINFO:
1864 if (token == TdsEnums.SQLERROR) {
1865 stateObj._errorTokenReceived = true; // Keep track of the fact error token was received - for Done processing.
1868 SqlError error;
1869 if (!TryProcessError(token, stateObj, out error)) {
1870 return false;
1873 if (token == TdsEnums.SQLINFO && stateObj._accumulateInfoEvents)
1875 Debug.Assert(error.Class < TdsEnums.MIN_ERROR_CLASS, "INFO with class > TdsEnums.MIN_ERROR_CLASS");
1877 if (stateObj._pendingInfoEvents == null)
1878 stateObj._pendingInfoEvents = new List<SqlError>();
1879 stateObj._pendingInfoEvents.Add(error);
1880 stateObj._syncOverAsync = true;
1881 break;
1884 if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) {
1885 // If FireInfoMessageEventOnUserErrors is true, we have to fire event without waiting.
1886 // Otherwise we can go ahead and add it to errors/warnings collection.
1887 SqlConnection connection = null;
1888 if (_connHandler != null)
1889 connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref
1891 if ((connection != null) &&
1892 (connection.FireInfoMessageEventOnUserErrors == true) &&
1893 (error.Class <= TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS)) {
1894 // Fire SqlInfoMessage here
1895 FireInfoMessageEvent(connection, stateObj, error);
1897 else {
1898 // insert error/info into the appropriate exception - warning if info, exception if error
1899 if (error.Class < TdsEnums.MIN_ERROR_CLASS) {
1900 stateObj.AddWarning(error);
1902 else if (error.Class < TdsEnums.FATAL_ERROR_CLASS) {
1903 // VSTFDEVDIV 479643: continue results processing for all non-fatal errors (<20)
1905 stateObj.AddError(error);
1907 // Add it to collection - but do NOT change run behavior UNLESS
1908 // we are in an ExecuteReader call - at which time we will be throwing
1909 // anyways so we need to consume all errors. This is not the case
1910 // if we have already given out a reader. If we have already given out
1911 // a reader we need to throw the error but not halt further processing. We used to
1912 // halt processing and that was a bug preventing the user from
1913 // processing subsequent results.
1915 if (null != dataStream) { // Webdata 104560
1916 if (!dataStream.IsInitialized) {
1917 runBehavior = RunBehavior.UntilDone;
1921 else {
1922 stateObj.AddError(error);
1924 // Else we have a fatal error and we need to change the behavior
1925 // since we want the complete error information in the exception.
1926 // Besides - no further results will be received.
1927 runBehavior = RunBehavior.UntilDone;
1931 else if (error.Class >= TdsEnums.FATAL_ERROR_CLASS) {
1932 stateObj.AddError(error);
1934 break;
1937 case TdsEnums.SQLCOLINFO:
1939 if (null != dataStream) {
1940 _SqlMetaDataSet metaDataSet;
1941 if (!TryProcessColInfo(dataStream.MetaData, dataStream, stateObj, out metaDataSet)) {
1942 return false;
1944 if (!dataStream.TrySetMetaData(metaDataSet, false)) {
1945 return false;
1947 dataStream.BrowseModeInfoConsumed = true;
1949 else { // no dataStream
1950 if (!stateObj.TrySkipBytes(tokenLength)) {
1951 return false;
1954 break;
1957 case TdsEnums.SQLDONE:
1958 case TdsEnums.SQLDONEPROC:
1959 case TdsEnums.SQLDONEINPROC:
1961 // RunBehavior can be modified - see SQL BU DT 269516 & 290090
1962 if (!TryProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj)) {
1963 return false;
1965 if ((token == TdsEnums.SQLDONEPROC) && (cmdHandler != null)) {
1966 // If the current parse/read is for the results of describe parameter encryption RPC requests,
1967 // call a different handler which will update the describe parameter encryption RPC structures
1968 // with the results, instead of the actual user RPC requests.
1969 if (cmdHandler.IsDescribeParameterEncryptionRPCCurrentlyInProgress) {
1970 cmdHandler.OnDoneDescribeParameterEncryptionProc(stateObj);
1972 else {
1973 cmdHandler.OnDoneProc();
1977 break;
1980 case TdsEnums.SQLORDER:
1982 // don't do anything with the order token so read off the pipe
1983 if (!stateObj.TrySkipBytes(tokenLength)) {
1984 return false;
1986 break;
1989 case TdsEnums.SQLALTMETADATA:
1991 stateObj.CloneCleanupAltMetaDataSetArray();
1993 if (stateObj._cleanupAltMetaDataSetArray == null) {
1994 // create object on demand (lazy creation)
1995 stateObj._cleanupAltMetaDataSetArray = new _SqlMetaDataSetCollection();
1998 _SqlMetaDataSet cleanupAltMetaDataSet;
1999 if (!TryProcessAltMetaData(tokenLength, stateObj, out cleanupAltMetaDataSet)) {
2000 return false;
2003 stateObj._cleanupAltMetaDataSetArray.SetAltMetaData(cleanupAltMetaDataSet);
2004 if (null != dataStream) {
2005 byte metadataConsumedByte;
2006 if (!stateObj.TryPeekByte(out metadataConsumedByte)) {
2007 return false;
2009 if (!dataStream.TrySetAltMetaDataSet(cleanupAltMetaDataSet, (TdsEnums.SQLALTMETADATA != metadataConsumedByte))) {
2010 return false;
2014 break;
2017 case TdsEnums.SQLALTROW:
2019 if (!stateObj.TryStartNewRow(isNullCompressed:false)) { // altrows are not currently null compressed
2020 return false;
2023 // read will call run until dataReady. Must not read any data if returnimmetiately set
2024 if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) {
2025 ushort altRowId;
2026 if (!stateObj.TryReadUInt16(out altRowId)) { // get altRowId
2027 return false;
2030 if (!TrySkipRow(stateObj._cleanupAltMetaDataSetArray.GetAltMetaData(altRowId), stateObj)) { // skip altRow
2031 return false;
2034 else {
2035 dataReady = true;
2038 break;
2041 case TdsEnums.SQLENVCHANGE:
2043 // ENVCHANGE must be processed synchronously (since it can modify the state of many objects)
2044 stateObj._syncOverAsync = true;
2046 SqlEnvChange[] env;
2047 if (!TryProcessEnvChange(tokenLength, stateObj, out env)) {
2048 return false;
2051 for (int ii = 0; ii < env.Length; ii++) {
2052 if (env[ii] != null && !this.Connection.IgnoreEnvChange) {
2053 switch (env[ii].type) {
2054 case TdsEnums.ENV_BEGINTRAN:
2055 case TdsEnums.ENV_ENLISTDTC:
2056 // When we get notification from the server of a new
2057 // transaction, we move any pending transaction over to
2058 // the current transaction, then we store the token in it.
2059 // if there isn't a pending transaction, then it's either
2060 // a TSQL transaction or a distributed transaction.
2061 Debug.Assert(null == _currentTransaction, "non-null current transaction with an ENV Change");
2062 _currentTransaction = _pendingTransaction;
2063 _pendingTransaction = null;
2065 if (null != _currentTransaction) {
2066 _currentTransaction.TransactionId = env[ii].newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec.
2068 else {
2069 TransactionType transactionType = (TdsEnums.ENV_BEGINTRAN == env[ii].type) ? TransactionType.LocalFromTSQL : TransactionType.Distributed;
2070 _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env[ii].newLongValue);
2072 if (null != _statistics && !_statisticsIsInTransaction) {
2073 _statistics.SafeIncrement(ref _statistics._transactions);
2075 _statisticsIsInTransaction = true;
2076 _retainedTransactionId = SqlInternalTransaction.NullTransactionId;
2077 break;
2078 case TdsEnums.ENV_DEFECTDTC:
2079 case TdsEnums.ENV_TRANSACTIONENDED:
2080 case TdsEnums.ENV_COMMITTRAN:
2081 // SQLHOT 483
2082 // Must clear the retain id if the server-side transaction ends by anything other
2083 // than rollback.
2084 _retainedTransactionId = SqlInternalTransaction.NullTransactionId;
2085 goto case TdsEnums.ENV_ROLLBACKTRAN;
2086 case TdsEnums.ENV_ROLLBACKTRAN:
2087 // When we get notification of a completed transaction
2088 // we null out the current transaction.
2089 if (null != _currentTransaction) {
2090 #if DEBUG
2091 // Check null for case where Begin and Rollback obtained in the same message.
2092 if (SqlInternalTransaction.NullTransactionId != _currentTransaction.TransactionId) {
2093 Debug.Assert(_currentTransaction.TransactionId != env[ii].newLongValue, "transaction id's are not equal!");
2095 #endif
2097 if (TdsEnums.ENV_COMMITTRAN == env[ii].type) {
2098 _currentTransaction.Completed(TransactionState.Committed);
2100 else if (TdsEnums.ENV_ROLLBACKTRAN == env[ii].type) {
2101 // Hold onto transaction id if distributed tran is rolled back. This must
2102 // be sent to the server on subsequent executions even though the transaction
2103 // is considered to be rolled back.
2104 if (_currentTransaction.IsDistributed && _currentTransaction.IsActive) {
2105 _retainedTransactionId = env[ii].oldLongValue;
2107 _currentTransaction.Completed(TransactionState.Aborted);
2109 else {
2111 _currentTransaction.Completed(TransactionState.Unknown);
2113 _currentTransaction = null;
2115 _statisticsIsInTransaction = false;
2116 break;
2117 default:
2118 _connHandler.OnEnvChange(env[ii]);
2119 break;
2123 break;
2125 case TdsEnums.SQLLOGINACK:
2127 Bid.Trace("<sc.TdsParser.TryRun|SEC> Received login acknowledgement token\n");
2128 SqlLoginAck ack;
2129 if (!TryProcessLoginAck(stateObj, out ack)) {
2130 return false;
2133 _connHandler.OnLoginAck(ack);
2134 break;
2136 case TdsEnums.SQLFEATUREEXTACK:
2138 if (!TryProcessFeatureExtAck(stateObj)) {
2139 return false;
2141 break;
2143 case TdsEnums.SQLFEDAUTHINFO:
2145 _connHandler._federatedAuthenticationInfoReceived = true;
2146 SqlFedAuthInfo info;
2147 Bid.Trace("<sc.TdsParser.TryRun|SEC> Received federated authentication info token\n");
2148 if (!TryProcessFedAuthInfo(stateObj, tokenLength, out info)) {
2149 return false;
2151 _connHandler.OnFedAuthInfo(info);
2152 break;
2154 case TdsEnums.SQLSESSIONSTATE:
2156 if (!TryProcessSessionState(stateObj, tokenLength, _connHandler._currentSessionData)) {
2157 return false;
2159 break;
2161 case TdsEnums.SQLCOLMETADATA:
2163 if (tokenLength != TdsEnums.VARNULL) {
2164 _SqlMetaDataSet metadata;
2165 if (!TryProcessMetaData(tokenLength, stateObj, out metadata,
2166 cmdHandler != null ? cmdHandler.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting)) {
2167 return false;
2169 stateObj._cleanupMetaData = metadata;
2171 else {
2172 if (cmdHandler != null) {
2173 stateObj._cleanupMetaData = cmdHandler.MetaData;
2177 if (null != dataStream) {
2178 byte peekedToken;
2179 if (!stateObj.TryPeekByte(out peekedToken)) { // temporarily cache next byte
2180 return false;
2183 if (!dataStream.TrySetMetaData(stateObj._cleanupMetaData, (TdsEnums.SQLTABNAME == peekedToken || TdsEnums.SQLCOLINFO == peekedToken))) {
2184 return false;
2187 else if (null != bulkCopyHandler) {
2188 bulkCopyHandler.SetMetaData(stateObj._cleanupMetaData);
2190 break;
2192 case TdsEnums.SQLROW:
2193 case TdsEnums.SQLNBCROW:
2195 Debug.Assert(stateObj._cleanupMetaData != null, "Reading a row, but the metadata is null");
2197 if (token == TdsEnums.SQLNBCROW) {
2198 if (!stateObj.TryStartNewRow(isNullCompressed: true, nullBitmapColumnsCount: stateObj._cleanupMetaData.Length)) {
2199 return false;
2202 else {
2203 if (!stateObj.TryStartNewRow(isNullCompressed:false)) {
2204 return false;
2208 if (null != bulkCopyHandler) {
2210 if (!TryProcessRow(stateObj._cleanupMetaData, bulkCopyHandler.CreateRowBuffer(), bulkCopyHandler.CreateIndexMap(), stateObj)) {
2211 return false;
2214 else if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) {
2215 if (!TrySkipRow(stateObj._cleanupMetaData, stateObj)) { // skip rows
2216 return false;
2219 else {
2220 dataReady = true;
2223 if (_statistics != null) {
2224 _statistics.WaitForDoneAfterRow = true;
2226 break;
2228 case TdsEnums.SQLRETURNSTATUS:
2229 int status;
2230 if (!stateObj.TryReadInt32(out status)) {
2231 return false;
2233 if (cmdHandler != null) {
2234 cmdHandler.OnReturnStatus(status);
2236 break;
2237 case TdsEnums.SQLRETURNVALUE:
2239 SqlReturnValue returnValue;
2240 if (!TryProcessReturnValue(tokenLength, stateObj, out returnValue,
2241 cmdHandler != null ? cmdHandler.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting)) {
2242 return false;
2244 if (cmdHandler != null) {
2245 cmdHandler.OnReturnValue(returnValue, stateObj);
2247 break;
2249 case TdsEnums.SQLSSPI:
2251 // token length is length of SSPI data - call ProcessSSPI with it
2253 Debug.Assert(stateObj._syncOverAsync, "ProcessSSPI does not support retry, do not attempt asynchronously");
2254 stateObj._syncOverAsync = true;
2256 ProcessSSPI(tokenLength);
2257 break;
2259 case TdsEnums.SQLTABNAME:
2261 if (null != dataStream) {
2262 MultiPartTableName[] tableNames;
2263 if (!TryProcessTableName(tokenLength, stateObj, out tableNames)) {
2264 return false;
2266 dataStream.TableNames = tableNames;
2268 else {
2269 if (!stateObj.TrySkipBytes(tokenLength)) {
2270 return false;
2273 break;
2276 default:
2277 Debug.Assert(false, "Unhandled token: " + token.ToString(CultureInfo.InvariantCulture));
2278 break;
2281 Debug.Assert(stateObj._pendingData || !dataReady, "dataReady is set, but there is no pending data");
2284 // Loop while data pending & runbehavior not return immediately, OR
2285 // if in attention case, loop while no more pending data & attention has not yet been
2286 // received.
2287 while ((stateObj._pendingData &&
2288 (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))) ||
2289 (!stateObj._pendingData && stateObj._attentionSent && !stateObj._attentionReceived));
2291 #if DEBUG
2292 if ((stateObj._pendingData) && (!dataReady)) {
2293 byte token;
2294 if (!stateObj.TryPeekByte(out token)) {
2295 return false;
2297 Debug.Assert(IsValidTdsToken(token), string.Format("DataReady is false, but next token is not valid: {0,-2:X2}", token));
2299 #endif
2301 if (!stateObj._pendingData) {
2302 if (null != CurrentTransaction) {
2303 CurrentTransaction.Activate();
2307 // if we recieved an attention (but this thread didn't send it) then
2308 // we throw an Operation Cancelled error
2309 if (stateObj._attentionReceived) {
2310 // Dev11 #344723: SqlClient stress hang System_Data!Tcp::ReadSync via a call to SqlDataReader::Close
2311 // Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent
2312 SpinWait.SpinUntil(() => !stateObj._attentionSending);
2314 Debug.Assert(stateObj._attentionSent, "Attention ACK has been received without attention sent");
2315 if (stateObj._attentionSent) {
2316 // Reset attention state.
2317 stateObj._attentionSent = false;
2318 stateObj._attentionReceived = false;
2320 if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj._internalTimeout) {
2321 // Add attention error to collection - if not RunBehavior.Clean!
2322 stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.OperationCancelled(), "", 0));
2327 if (stateObj.HasErrorOrWarning) {
2328 ThrowExceptionAndWarning(stateObj);
2330 return true;
2333 private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange[] sqlEnvChange) {
2334 // There could be multiple environment change messages following this token.
2335 byte byteLength;
2336 int processedLength = 0;
2337 int nvalues = 0;
2338 SqlEnvChange[] envarray = new SqlEnvChange[3]; // Why is this hardcoded to 3?
2340 sqlEnvChange = null;
2342 while (tokenLength > processedLength) {
2344 if (nvalues >= envarray.Length) {
2345 // This is a rare path. Most of the time we will have 1 or 2 envchange data streams.
2346 SqlEnvChange[] newenvarray = new SqlEnvChange[envarray.Length + 3];
2348 for (int ii = 0; ii < envarray.Length; ii++)
2349 newenvarray[ii] = envarray[ii];
2351 envarray = newenvarray;
2354 SqlEnvChange env = new SqlEnvChange();
2356 if (!stateObj.TryReadByte(out env.type)) {
2357 return false;
2360 envarray[nvalues] = env;
2361 nvalues++;
2363 switch (env.type) {
2364 case TdsEnums.ENV_DATABASE:
2365 case TdsEnums.ENV_LANG:
2366 if (!TryReadTwoStringFields(env, stateObj)) {
2367 return false;
2369 break;
2371 case TdsEnums.ENV_CHARSET:
2372 // we copied this behavior directly from luxor - see charset envchange
2373 // section from sqlctokn.c
2374 Debug.Assert(!_isShiloh, "Received ENV_CHARSET on non 7.0 server!");
2375 if (!TryReadTwoStringFields(env, stateObj)) {
2376 return false;
2378 if (env.newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) {
2379 _defaultCodePage = TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE;
2380 _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage);
2382 else {
2383 Debug.Assert(env.newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10");
2385 string stringCodePage = env.newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET);
2387 _defaultCodePage = Int32.Parse(stringCodePage, NumberStyles.Integer, CultureInfo.InvariantCulture);
2388 _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage);
2391 break;
2393 case TdsEnums.ENV_PACKETSIZE:
2394 // take care of packet size right here
2395 Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
2396 if (!TryReadTwoStringFields(env, stateObj)) {
2397 // Changing packet size does not support retry, should not pend"
2398 throw SQL.SynchronousCallMayNotPend();
2400 // Only set on physical state object - this should only occur on LoginAck prior
2401 // to MARS initialization!
2402 Int32 packetSize = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
2404 if (_physicalStateObj.SetPacketSize(packetSize)) {
2405 // If packet size changed, we need to release our SNIPackets since
2406 // those are tied to packet size of connection.
2407 _physicalStateObj.ClearAllWritePackets();
2409 // Update SNI ConsumerInfo value to be resulting packet size
2410 UInt32 unsignedPacketSize = (UInt32) packetSize;
2411 UInt32 result = SNINativeMethodWrapper.SNISetInfo(_physicalStateObj.Handle, SNINativeMethodWrapper.QTypes.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize);
2413 Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo");
2416 break;
2418 case TdsEnums.ENV_LOCALEID:
2421 if (!TryReadTwoStringFields(env, stateObj)) {
2422 return false;
2424 _defaultLCID = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
2425 break;
2427 case TdsEnums.ENV_COMPFLAGS:
2428 if (!TryReadTwoStringFields(env, stateObj)) {
2429 return false;
2431 break;
2433 case TdsEnums.ENV_COLLATION:
2434 Debug.Assert(env.newLength == 5 || env.newLength == 0, "Improper length in new collation!");
2435 if (!stateObj.TryReadByte(out byteLength)) {
2436 return false;
2438 env.newLength = byteLength;
2439 if (env.newLength == 5) {
2440 if (!TryProcessCollation(stateObj, out env.newCollation)) {
2441 return false;
2444 // give the parser the new collation values in case parameters don't specify one
2445 _defaultCollation = env.newCollation;
2446 int newCodePage = GetCodePage(env.newCollation, stateObj);
2447 if (newCodePage != _defaultCodePage) {
2448 _defaultCodePage = newCodePage;
2449 _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage);
2451 _defaultLCID = env.newCollation.LCID;
2454 if (!stateObj.TryReadByte(out byteLength)) {
2455 return false;
2457 env.oldLength = byteLength;
2458 Debug.Assert(env.oldLength == 5 || env.oldLength == 0, "Improper length in old collation!");
2459 if (env.oldLength == 5) {
2460 if (!TryProcessCollation(stateObj, out env.oldCollation)) {
2461 return false;
2465 env.length = 3 + env.newLength + env.oldLength;
2466 break;
2468 case TdsEnums.ENV_BEGINTRAN:
2469 case TdsEnums.ENV_COMMITTRAN:
2470 case TdsEnums.ENV_ROLLBACKTRAN:
2471 case TdsEnums.ENV_ENLISTDTC:
2472 case TdsEnums.ENV_DEFECTDTC:
2473 case TdsEnums.ENV_TRANSACTIONENDED:
2474 Debug.Assert(_isYukon, "Received new ENVCHANGE transaction/DTC token on pre 9.0 server!");
2476 if (!stateObj.TryReadByte(out byteLength)) {
2477 return false;
2479 env.newLength = byteLength;
2480 Debug.Assert(env.newLength == 0 || env.newLength == 8, "Improper length for new transaction id!");
2482 if (env.newLength > 0) {
2483 if (!stateObj.TryReadInt64(out env.newLongValue)) {
2484 return false;
2486 Debug.Assert(env.newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id.
2488 else {
2489 env.newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id.
2492 if (!stateObj.TryReadByte(out byteLength)) {
2493 return false;
2495 env.oldLength = byteLength;
2496 Debug.Assert(env.oldLength == 0 || env.oldLength == 8, "Improper length for old transaction id!");
2498 if (env.oldLength > 0) {
2499 if (!stateObj.TryReadInt64(out env.oldLongValue)) {
2500 return false;
2502 Debug.Assert(env.oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id.
2504 else {
2505 env.oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id.
2508 // env.length includes 1 byte type token
2509 env.length = 3 + env.newLength + env.oldLength;
2510 break;
2512 case TdsEnums.ENV_LOGSHIPNODE:
2513 // env.newBinValue is secondary node, env.oldBinValue is witness node
2514 // comes before LoginAck so we can't assert this
2515 if (!TryReadTwoStringFields(env, stateObj)) {
2516 return false;
2518 break;
2520 case TdsEnums.ENV_PROMOTETRANSACTION:
2521 Debug.Assert(_isYukon, "Received new ENVCHANGE tokens on pre 9.0 server!");
2523 if (!stateObj.TryReadInt32(out env.newLength)) { // new value has 4 byte length
2524 return false;
2526 env.newBinValue = new byte[env.newLength];
2527 if (!stateObj.TryReadByteArray(env.newBinValue, 0, env.newLength)) { // read new value with 4 byte length
2528 return false;
2531 if (!stateObj.TryReadByte(out byteLength)) {
2532 return false;
2534 env.oldLength = byteLength;
2535 Debug.Assert(0 == env.oldLength, "old length should be zero");
2537 // env.length includes 1 byte for type token
2538 env.length = 5 + env.newLength;
2539 break;
2541 case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS:
2542 case TdsEnums.ENV_SPRESETCONNECTIONACK:
2544 Debug.Assert(_isYukon, "Received new ENVCHANGE tokens on pre 9.0 server!");
2545 if (!TryReadTwoBinaryFields(env, stateObj)) {
2546 return false;
2548 break;
2550 case TdsEnums.ENV_USERINSTANCE:
2551 Debug.Assert(!_isYukon, "Received ENV_USERINSTANCE on non 9.0 server!");
2552 if (!TryReadTwoStringFields(env, stateObj)) {
2553 return false;
2555 break;
2557 case TdsEnums.ENV_ROUTING:
2558 ushort newLength;
2559 if (!stateObj.TryReadUInt16(out newLength)) {
2560 return false;
2562 env.newLength = newLength;
2563 byte protocol;
2564 if (!stateObj.TryReadByte(out protocol)) {
2565 return false;
2567 ushort port;
2568 if (!stateObj.TryReadUInt16(out port)) {
2569 return false;
2571 UInt16 serverLen;
2572 if (!stateObj.TryReadUInt16(out serverLen)) {
2573 return false;
2575 string serverName;
2576 if (!stateObj.TryReadString(serverLen, out serverName)) {
2577 return false;
2579 env.newRoutingInfo = new RoutingInfo(protocol, port, serverName);
2580 UInt16 oldLength;
2581 if (!stateObj.TryReadUInt16(out oldLength)) {
2582 return false;
2584 if (!stateObj.TrySkipBytes(oldLength)) {
2585 return false;
2587 env.length = env.newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength]
2588 break;
2590 default:
2591 Debug.Assert(false, "Unknown environment change token: " + env.type);
2592 break;
2594 processedLength += env.length;
2597 sqlEnvChange = envarray;
2598 return true;
2601 private bool TryReadTwoBinaryFields(SqlEnvChange env, TdsParserStateObject stateObj) {
2602 // Used by ProcessEnvChangeToken
2603 byte byteLength;
2604 if (!stateObj.TryReadByte(out byteLength)) {
2605 return false;
2607 env.newLength = byteLength;
2608 env.newBinValue = new byte[env.newLength];
2609 if (!stateObj.TryReadByteArray(env.newBinValue, 0, env.newLength)) {
2610 return false;
2612 if (!stateObj.TryReadByte(out byteLength)) {
2613 return false;
2615 env.oldLength = byteLength;
2616 env.oldBinValue = new byte[env.oldLength];
2617 if (!stateObj.TryReadByteArray(env.oldBinValue, 0, env.oldLength)) {
2618 return false;
2621 // env.length includes 1 byte type token
2622 env.length = 3 + env.newLength + env.oldLength;
2623 return true;
2626 private bool TryReadTwoStringFields(SqlEnvChange env, TdsParserStateObject stateObj) {
2627 // Used by ProcessEnvChangeToken
2628 byte newLength, oldLength;
2629 string newValue, oldValue;
2630 if (!stateObj.TryReadByte(out newLength)) {
2631 return false;
2633 if (!stateObj.TryReadString(newLength, out newValue)) {
2634 return false;
2636 if (!stateObj.TryReadByte(out oldLength)) {
2637 return false;
2639 if (!stateObj.TryReadString(oldLength, out oldValue)) {
2640 return false;
2643 env.newLength = newLength;
2644 env.newValue = newValue;
2645 env.oldLength = oldLength;
2646 env.oldValue = oldValue;
2648 // env.length includes 1 byte type token
2649 env.length = 3 + env.newLength * 2 + env.oldLength * 2;
2650 return true;
2653 private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavior run, TdsParserStateObject stateObj) {
2654 ushort curCmd;
2655 ushort status;
2656 int count;
2658 // Can't retry TryProcessDone
2659 stateObj._syncOverAsync = true;
2661 // status
2662 // command
2663 // rowcount (valid only if DONE_COUNT bit is set)
2665 if (!stateObj.TryReadUInt16(out status)) {
2666 return false;
2668 if (!stateObj.TryReadUInt16(out curCmd)) {
2669 return false;
2672 if (_isYukon) {
2673 long longCount;
2674 if (!stateObj.TryReadInt64(out longCount)) {
2675 return false;
2677 count = (int) longCount;
2679 else {
2680 if (!stateObj.TryReadInt32(out count)) {
2681 return false;
2683 // If we haven't yet completed processing login token stream yet, we may be talking to a Yukon server
2684 // In that case we still have to read another 4 bytes
2685 // But don't try to read beyond the TDS stream in this case, because it generates errors if login failed.
2686 if ( _state == TdsParserState.OpenNotLoggedIn) {
2687 // Login incomplete, if we are reading from Yukon we need to read another int
2688 if (stateObj._inBytesRead > stateObj._inBytesUsed) {
2689 byte b;
2690 if (!stateObj.TryPeekByte(out b)) {
2691 return false;
2693 if (b == 0) {
2694 // This is an invalid token value
2695 if (!stateObj.TryReadInt32(out count)) {
2696 return false;
2703 // We get a done token with the attention bit set
2704 if (TdsEnums.DONE_ATTN == (status & TdsEnums.DONE_ATTN)) {
2705 Debug.Assert(TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE),"Not expecting DONE_MORE when receiving DONE_ATTN");
2706 Debug.Assert(stateObj._attentionSent, "Received attention done without sending one!");
2707 stateObj._attentionReceived = true;
2708 Debug.Assert(stateObj._inBytesUsed == stateObj._inBytesRead && stateObj._inBytesPacket == 0, "DONE_ATTN received with more data left on wire");
2710 if ((null != cmd) && (TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) {
2711 if (curCmd != TdsEnums.SELECT) {
2712 if (cmd.IsDescribeParameterEncryptionRPCCurrentlyInProgress) {
2713 // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise.
2714 cmd.RowsAffectedByDescribeParameterEncryption = count;
2716 else {
2717 cmd.InternalRecordsAffected = count;
2720 // Skip the bogus DONE counts sent by the server
2721 if (stateObj._receivedColMetaData || (curCmd != TdsEnums.SELECT)) {
2722 cmd.OnStatementCompleted(count);
2726 stateObj._receivedColMetaData = false;
2728 // Surface exception for DONE_ERROR in the case we did not receive an error token
2729 // in the stream, but an error occurred. In these cases, we throw a general server error. The
2730 // situations where this can occur are: an invalid buffer received from client, login error
2731 // and the server refused our connection, and the case where we are trying to log in but
2732 // the server has reached its max connection limit. Bottom line, we need to throw general
2733 // error in the cases where we did not receive a error token along with the DONE_ERROR.
2734 if ((TdsEnums.DONE_ERROR == (TdsEnums.DONE_ERROR & status)) && stateObj.ErrorCount == 0 &&
2735 stateObj._errorTokenReceived == false && (RunBehavior.Clean != (RunBehavior.Clean & run))) {
2736 stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0));
2738 if (null != reader) { // SQL BU DT 269516
2739 if (!reader.IsInitialized) {
2740 run = RunBehavior.UntilDone;
2745 // Similar to above, only with a more severe error. In this case, if we received
2746 // the done_srverror, this exception will be added to the collection regardless.
2747 // MDAC #93896. Also, per Ashwin, the server will always break the connection in this case.
2748 if ((TdsEnums.DONE_SRVERROR == (TdsEnums.DONE_SRVERROR & status)) && (RunBehavior.Clean != (RunBehavior.Clean & run))) {
2749 stateObj.AddError(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0));
2751 if (null != reader) { // SQL BU DT 269516
2752 if (!reader.IsInitialized) {
2753 run = RunBehavior.UntilDone;
2758 ProcessSqlStatistics(curCmd, status, count);
2760 // stop if the DONE_MORE bit isn't set (see above for attention handling)
2761 if (TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE)) {
2762 stateObj._errorTokenReceived = false;
2763 if (stateObj._inBytesUsed >= stateObj._inBytesRead) {
2764 stateObj._pendingData = false;
2768 // _pendingData set by e.g. 'TdsExecuteSQLBatch'
2769 // _hasOpenResult always set to true by 'WriteMarsHeader'
2771 if (!stateObj._pendingData && stateObj._hasOpenResult) {
2773 Debug.Assert(!((sqlTransaction != null && _distributedTransaction != null) ||
2774 (_userStartedLocalTransaction != null && _distributedTransaction != null))
2775 , "ProcessDone - have both distributed and local transactions not null!");
2776 */ // WebData 112722
2778 stateObj.DecrementOpenResultCount();
2781 return true;
2784 private void ProcessSqlStatistics(ushort curCmd, ushort status, int count) {
2785 // SqlStatistics bookkeeping stuff
2787 if (null != _statistics) {
2788 // any done after row(s) counts as a resultset
2789 if (_statistics.WaitForDoneAfterRow) {
2790 _statistics.SafeIncrement(ref _statistics._sumResultSets);
2791 _statistics.WaitForDoneAfterRow = false;
2794 // clear row count DONE_COUNT flag is not set
2795 if (!(TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) {
2796 count = 0;
2799 switch (curCmd) {
2800 case TdsEnums.INSERT:
2801 case TdsEnums.DELETE:
2802 case TdsEnums.UPDATE:
2803 case TdsEnums.MERGE:
2804 _statistics.SafeIncrement(ref _statistics._iduCount);
2805 _statistics.SafeAdd(ref _statistics._iduRows, count);
2806 if (!_statisticsIsInTransaction) {
2807 _statistics.SafeIncrement(ref _statistics._transactions);
2810 break;
2812 case TdsEnums.SELECT:
2813 _statistics.SafeIncrement(ref _statistics._selectCount);
2814 _statistics.SafeAdd(ref _statistics._selectRows, count);
2815 break;
2817 case TdsEnums.BEGINXACT:
2818 if (!_statisticsIsInTransaction) {
2819 _statistics.SafeIncrement(ref _statistics._transactions);
2821 _statisticsIsInTransaction = true;
2822 break;
2824 case TdsEnums.OPENCURSOR:
2825 _statistics.SafeIncrement(ref _statistics._cursorOpens);
2826 break;
2828 case TdsEnums.ABORT:
2829 _statisticsIsInTransaction = false;
2830 break;
2832 case TdsEnums.ENDXACT:
2833 _statisticsIsInTransaction = false;
2834 break;
2835 } // switch
2837 else {
2838 switch (curCmd) {
2839 case TdsEnums.BEGINXACT:
2840 _statisticsIsInTransaction = true;
2841 break;
2843 case TdsEnums.ABORT:
2844 case TdsEnums.ENDXACT:
2845 _statisticsIsInTransaction = false;
2846 break;
2851 private bool TryProcessFeatureExtAck(TdsParserStateObject stateObj) {
2852 // read feature ID
2853 byte featureId;
2854 do {
2855 if (!stateObj.TryReadByte(out featureId)) {
2856 return false;
2858 if (featureId != TdsEnums.FEATUREEXT_TERMINATOR) {
2859 UInt32 dataLen;
2860 if (!stateObj.TryReadUInt32(out dataLen)) {
2861 return false;
2863 byte[] data = new byte[dataLen];
2864 if (dataLen > 0) {
2865 if (!stateObj.TryReadByteArray(data, 0, checked ((int)dataLen))) {
2866 return false;
2869 _connHandler.OnFeatureExtAck(featureId, data);
2871 } while (featureId != TdsEnums.FEATUREEXT_TERMINATOR);
2873 // Check if column encryption was on and feature wasn't acknowledged.
2874 if (_connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled && !IsColumnEncryptionSupported) {
2875 throw SQL.TceNotSupported ();
2878 return true;
2881 private bool TryProcessSessionState(TdsParserStateObject stateObj, int length, SessionData sdata) {
2882 if (length < 5) {
2883 throw SQL.ParsingErrorLength(ParsingErrorState.SessionStateLengthTooShort, length);
2885 UInt32 seqNum;
2886 if (!stateObj.TryReadUInt32(out seqNum)) {
2887 return false;
2889 if (seqNum == UInt32.MaxValue) {
2890 _connHandler.DoNotPoolThisConnection();
2892 byte status;
2893 if (!stateObj.TryReadByte(out status)) {
2894 return false;
2896 if (status > 1) {
2897 throw SQL.ParsingErrorStatus(ParsingErrorState.SessionStateInvalidStatus, status);
2899 bool recoverable = status != 0;
2900 length -= 5;
2901 while (length > 0) {
2902 byte stateId;
2903 if (!stateObj.TryReadByte(out stateId)) {
2904 return false;
2906 int stateLen;
2907 byte stateLenByte;
2908 if (!stateObj.TryReadByte(out stateLenByte)) {
2909 return false;
2911 if (stateLenByte < 0xFF) {
2912 stateLen = stateLenByte;
2914 else {
2915 if (!stateObj.TryReadInt32(out stateLen)) {
2916 return false;
2919 byte[] buffer = null;
2920 lock (sdata._delta) {
2921 if (sdata._delta[stateId] == null) {
2922 buffer = new byte[stateLen];
2923 sdata._delta[stateId] = new SessionStateRecord { _version = seqNum, _dataLength = stateLen, _data = buffer, _recoverable = recoverable };
2924 sdata._deltaDirty = true;
2925 if (!recoverable) {
2926 checked { sdata._unrecoverableStatesCount++; }
2929 else {
2930 if (sdata._delta[stateId]._version <= seqNum) {
2931 SessionStateRecord sv = sdata._delta[stateId];
2932 sv._version = seqNum;
2933 sv._dataLength = stateLen;
2934 if (sv._recoverable != recoverable) {
2935 if (recoverable) {
2936 Debug.Assert(sdata._unrecoverableStatesCount > 0, "Unrecoverable states count >0");
2937 sdata._unrecoverableStatesCount--;
2939 else {
2940 checked { sdata._unrecoverableStatesCount++; }
2942 sv._recoverable = recoverable;
2944 buffer = sv._data;
2945 if (buffer.Length < stateLen) {
2946 buffer = new byte[stateLen];
2947 sv._data = buffer;
2952 if (buffer != null) {
2953 if (!stateObj.TryReadByteArray(buffer, 0, stateLen)) {
2954 return false;
2957 else {
2958 if (!stateObj.TrySkipBytes(stateLen))
2959 return false;
2962 if (stateLenByte < 0xFF) {
2963 length -= 2 + stateLen;
2965 else {
2966 length -= 6 + stateLen;
2969 sdata.AssertUnrecoverableStateCountIsCorrect();
2971 return true;
2974 private bool TryProcessLoginAck(TdsParserStateObject stateObj, out SqlLoginAck sqlLoginAck) {
2975 SqlLoginAck a = new SqlLoginAck();
2977 sqlLoginAck = null;
2979 // read past interface type and version
2980 if (!stateObj.TrySkipBytes(1)) {
2981 return false;
2984 byte[] b = new byte[TdsEnums.VERSION_SIZE];
2985 if (!stateObj.TryReadByteArray(b, 0, b.Length)) {
2986 return false;
2988 a.tdsVersion = (UInt32)((((((b[0]<<8)|b[1])<<8)|b[2])<<8)|b[3]); // bytes are in motorola order (high byte first)
2989 UInt32 majorMinor = a.tdsVersion & 0xff00ffff;
2990 UInt32 increment = (a.tdsVersion >> 16) & 0xff;
2992 // Server responds:
2993 // 0x07000000 -> Sphinx // Notice server response format is different for bwd compat
2994 // 0x07010000 -> Shiloh RTM // Notice server response format is different for bwd compat
2995 // 0x71000001 -> Shiloh SP1
2996 // 0x72xx0002 -> Yukon RTM
2997 // information provided by S. Ashwin
2999 switch (majorMinor) {
3000 case TdsEnums.SPHINXORSHILOH_MAJOR<<24|TdsEnums.DEFAULT_MINOR: // Sphinx & Shiloh RTM
3001 // note that sphinx and shiloh_rtm can only be distinguished by the increment
3002 switch (increment) {
3003 case TdsEnums.SHILOH_INCREMENT:
3004 _isShiloh = true;
3005 break;
3006 case TdsEnums.SPHINX_INCREMENT:
3007 // no flag will be set
3008 break;
3009 default:
3010 throw SQL.InvalidTDSVersion();
3012 break;
3013 case TdsEnums.SHILOHSP1_MAJOR<<24|TdsEnums.SHILOHSP1_MINOR: // Shiloh SP1
3014 if (increment != TdsEnums.SHILOHSP1_INCREMENT) { throw SQL.InvalidTDSVersion(); }
3015 _isShilohSP1 = true;
3016 break;
3017 case TdsEnums.YUKON_MAJOR<<24|TdsEnums.YUKON_RTM_MINOR: // Yukon
3018 if (increment != TdsEnums.YUKON_INCREMENT) { throw SQL.InvalidTDSVersion(); }
3019 _isYukon = true;
3020 break;
3021 case TdsEnums.KATMAI_MAJOR<<24|TdsEnums.KATMAI_MINOR:
3022 if (increment != TdsEnums.KATMAI_INCREMENT) { throw SQL.InvalidTDSVersion(); }
3023 _isKatmai = true;
3024 break;
3025 case TdsEnums.DENALI_MAJOR << 24|TdsEnums.DENALI_MINOR:
3026 if (increment != TdsEnums.DENALI_INCREMENT) { throw SQL.InvalidTDSVersion(); }
3027 _isDenali = true;
3028 break;
3029 default:
3030 throw SQL.InvalidTDSVersion();
3033 _isKatmai |= _isDenali;
3034 _isYukon |= _isKatmai;
3035 _isShilohSP1 |= _isYukon; // includes all lower versions
3036 _isShiloh |= _isShilohSP1; //
3038 a.isVersion8 = _isShiloh;
3040 stateObj._outBytesUsed = stateObj._outputHeaderLen;
3041 byte len;
3042 if (!stateObj.TryReadByte(out len)) {
3043 return false;
3046 if (!stateObj.TryReadString(len, out a.programName)) {
3047 return false;
3049 if (!stateObj.TryReadByte(out a.majorVersion)) {
3050 return false;
3052 if (!stateObj.TryReadByte(out a.minorVersion)) {
3053 return false;
3055 byte buildNumHi, buildNumLo;
3056 if (!stateObj.TryReadByte(out buildNumHi)) {
3057 return false;
3059 if (!stateObj.TryReadByte(out buildNumLo)) {
3060 return false;
3063 a.buildNum = (short)((buildNumHi << 8) + buildNumLo);
3065 Debug.Assert(_state == TdsParserState.OpenNotLoggedIn, "ProcessLoginAck called with state not TdsParserState.OpenNotLoggedIn");
3066 _state = TdsParserState.OpenLoggedIn;
3068 if (_isYukon) {
3069 if (_fMARS) {
3070 _resetConnectionEvent = new AutoResetEvent(true);
3074 // Fail if SSE UserInstance and we have not received this info.
3075 if ( _connHandler.ConnectionOptions.UserInstance &&
3076 ADP.IsEmpty(_connHandler.InstanceName)) {
3077 stateObj.AddError(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, Server, SQLMessage.UserInstanceFailure(), "", 0));
3078 ThrowExceptionAndWarning(stateObj);
3081 sqlLoginAck = a;
3082 return true;
3085 private bool TryProcessFedAuthInfo(TdsParserStateObject stateObj, int tokenLen, out SqlFedAuthInfo sqlFedAuthInfo) {
3086 sqlFedAuthInfo = null;
3087 SqlFedAuthInfo tempFedAuthInfo = new SqlFedAuthInfo();
3089 // Skip reading token length, since it has already been read in caller
3091 if (Bid.AdvancedOn) {
3092 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> FEDAUTHINFO token stream length = {0}\n", tokenLen);
3095 if (tokenLen < sizeof(uint)) {
3096 // the token must at least contain a DWORD indicating the number of info IDs
3097 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream length too short for CountOfInfoIDs.\n");
3098 throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForCountOfInfoIds, tokenLen);
3101 // read how many FedAuthInfo options there are
3102 uint optionsCount;
3103 if (!stateObj.TryReadUInt32(out optionsCount)) {
3104 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> Failed to read CountOfInfoIDs in FEDAUTHINFO token stream.\n");
3105 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadCountOfInfoIds);
3107 tokenLen -= sizeof(uint); // remaining length is shortened since we read optCount
3109 if (Bid.AdvancedOn) {
3110 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> CountOfInfoIDs = {0}\n", optionsCount.ToString(CultureInfo.InvariantCulture));
3113 if (tokenLen > 0) {
3114 // read the rest of the token
3115 byte[] tokenData = new byte[tokenLen];
3116 int totalRead = 0;
3117 bool successfulRead = stateObj.TryReadByteArray(tokenData, 0, tokenLen, out totalRead);
3119 if (Bid.AdvancedOn) {
3120 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> Read rest of FEDAUTHINFO token stream: {0}\n", BitConverter.ToString(tokenData, 0, totalRead));
3123 if (!successfulRead || totalRead != tokenLen) {
3124 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> Failed to read FEDAUTHINFO token stream. Attempted to read {0} bytes, actually read {1}\n", tokenLen, totalRead);
3125 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadTokenStream);
3128 // each FedAuthInfoOpt is 9 bytes:
3129 // 1 byte for FedAuthInfoID
3130 // 4 bytes for FedAuthInfoDataLen
3131 // 4 bytes for FedAuthInfoDataOffset
3132 // So this is the index in tokenData for the i-th option
3133 const uint optionSize = 9;
3135 // the total number of bytes for all FedAuthInfoOpts together
3136 uint totalOptionsSize = checked(optionsCount * optionSize);
3138 for (uint i = 0; i < optionsCount; i++) {
3139 uint currentOptionOffset = checked(i * optionSize);
3141 byte id = tokenData[currentOptionOffset];
3142 uint dataLen = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 1)));
3143 uint dataOffset = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 5)));
3145 if (Bid.AdvancedOn) {
3146 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> FedAuthInfoOpt: ID={0}, DataLen={1}, Offset={2}\n", id, dataLen.ToString(CultureInfo.InvariantCulture), dataOffset.ToString(CultureInfo.InvariantCulture));
3149 // offset is measured from optCount, so subtract to make offset measured
3150 // from the beginning of tokenData
3151 checked {
3152 dataOffset -= sizeof(uint);
3155 // if dataOffset points to a region within FedAuthInfoOpt or after the end of the token, throw
3156 if (dataOffset < totalOptionsSize || dataOffset >= tokenLen) {
3157 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FedAuthInfoDataOffset points to an invalid location.\n");
3158 throw SQL.ParsingErrorOffset(ParsingErrorState.FedAuthInfoInvalidOffset, unchecked((int)dataOffset));
3161 // try to read data and throw if the arguments are bad, meaning the server sent us a bad token
3162 string data;
3163 try {
3164 data = System.Text.Encoding.Unicode.GetString(tokenData, checked((int)dataOffset), checked((int)dataLen));
3166 catch (ArgumentOutOfRangeException e) {
3167 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> Failed to read FedAuthInfoData.\n");
3168 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadData, e);
3170 catch (ArgumentException e) {
3171 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FedAuthInfoData is not in unicode format.\n");
3172 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoDataNotUnicode, e);
3175 if (Bid.AdvancedOn) {
3176 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> FedAuthInfoData: {0}\n", data);
3179 // store data in tempFedAuthInfo
3180 switch ((TdsEnums.FedAuthInfoId)id) {
3181 case TdsEnums.FedAuthInfoId.Spn:
3182 tempFedAuthInfo.spn = data;
3183 break;
3184 case TdsEnums.FedAuthInfoId.Stsurl:
3185 tempFedAuthInfo.stsurl = data;
3186 break;
3187 default:
3188 if (Bid.AdvancedOn) {
3189 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> Ignoring unknown federated authentication info option: {0}\n", id);
3191 break;
3195 else {
3196 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream is not long enough to contain the data it claims to.\n");
3197 throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForData, tokenLen);
3200 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> Processed FEDAUTHINFO token stream: {0}\n", tempFedAuthInfo.ToString());
3202 if (String.IsNullOrWhiteSpace(tempFedAuthInfo.stsurl) || String.IsNullOrWhiteSpace(tempFedAuthInfo.spn)) {
3203 // We should be receiving both stsurl and spn
3204 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream does not contain both STSURL and SPN.\n");
3205 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoDoesNotContainStsurlAndSpn);
3208 sqlFedAuthInfo = tempFedAuthInfo;
3209 return true;
3212 internal bool TryProcessError(byte token, TdsParserStateObject stateObj, out SqlError error) {
3213 ushort shortLen;
3214 byte byteLen;
3215 int number;
3216 byte state;
3217 byte errorClass;
3219 error = null;
3221 if (!stateObj.TryReadInt32(out number)) {
3222 return false;
3224 if (!stateObj.TryReadByte(out state)) {
3225 return false;
3227 if (!stateObj.TryReadByte(out errorClass)) {
3228 return false;
3231 Debug.Assert(((errorClass >= TdsEnums.MIN_ERROR_CLASS) && token == TdsEnums.SQLERROR) ||
3232 ((errorClass < TdsEnums.MIN_ERROR_CLASS) && token == TdsEnums.SQLINFO), "class and token don't match!");
3234 if (!stateObj.TryReadUInt16(out shortLen)) {
3235 return false;
3237 string message;
3238 if (!stateObj.TryReadString(shortLen, out message)) {
3239 return false;
3242 if (!stateObj.TryReadByte(out byteLen)) {
3243 return false;
3246 string server;
3248 // MDAC bug #49307 - server sometimes does not send over server field! In those cases
3249 // we will use our locally cached value.
3250 if (byteLen == 0) {
3251 server = _server;
3253 else {
3254 if (!stateObj.TryReadString(byteLen, out server)) {
3255 return false;
3259 if (!stateObj.TryReadByte(out byteLen)) {
3260 return false;
3262 string procedure;
3263 if (!stateObj.TryReadString(byteLen, out procedure)) {
3264 return false;
3267 int line;
3268 if (_isYukon) {
3269 if (!stateObj.TryReadInt32(out line)) {
3270 return false;
3273 else {
3274 ushort shortLine;
3275 if (!stateObj.TryReadUInt16(out shortLine)) {
3276 return false;
3278 line = shortLine;
3279 // If we haven't yet completed processing login token stream yet, we may be talking to a Yukon server
3280 // In that case we still have to read another 2 bytes
3281 if ( _state == TdsParserState.OpenNotLoggedIn) {
3282 // Login incomplete
3283 byte b;
3284 if (!stateObj.TryPeekByte(out b)) {
3285 return false;
3287 if (b == 0) {
3288 // This is an invalid token value
3289 ushort value;
3290 if (!stateObj.TryReadUInt16(out value)) {
3291 return false;
3293 line = (line << 16) + value;
3298 error = new SqlError(number, state, errorClass, _server, message, procedure, line);
3299 return true;
3303 internal bool TryProcessReturnValue(int length,
3304 TdsParserStateObject stateObj,
3305 out SqlReturnValue returnValue,
3306 SqlCommandColumnEncryptionSetting columnEncryptionSetting) {
3307 returnValue = null;
3308 SqlReturnValue rec = new SqlReturnValue();
3309 rec.length = length; // In Yukon this length is -1
3310 if (_isYukon) {
3311 if (!stateObj.TryReadUInt16(out rec.parmIndex)) {
3312 return false;
3315 byte len;
3316 if (!stateObj.TryReadByte(out len)) { // Length of parameter name
3317 return false;
3320 rec.parameter = null;
3321 if (len > 0) {
3322 if (!stateObj.TryReadString(len, out rec.parameter)) {
3323 return false;
3327 // read status and ignore
3328 byte ignored;
3329 if (!stateObj.TryReadByte(out ignored)) {
3330 return false;
3333 UInt32 userType;
3335 // read user type - 4 bytes Yukon, 2 backwards
3336 if (IsYukonOrNewer) {
3337 if (!stateObj.TryReadUInt32(out userType)) {
3338 return false;
3341 else {
3342 ushort userTypeShort;
3343 if (!stateObj.TryReadUInt16(out userTypeShort)) {
3344 return false;
3346 userType = userTypeShort;
3349 // Read off the flags.
3350 // The first byte is ignored since it doesn't contain any interesting information.
3351 byte flags;
3352 if (!stateObj.TryReadByte(out flags)) {
3353 return false;
3356 if (!stateObj.TryReadByte(out flags)) {
3357 return false;
3360 // Check if the column is encrypted.
3361 if (_serverSupportsColumnEncryption) {
3362 rec.isEncrypted = (TdsEnums.IsEncrypted == (flags & TdsEnums.IsEncrypted));
3365 // read the type
3366 byte tdsType;
3367 if (!stateObj.TryReadByte(out tdsType)) {
3368 return false;
3371 // read the MaxLen
3372 // For xml datatpyes, there is no tokenLength
3373 int tdsLen;
3375 if (tdsType == TdsEnums.SQLXMLTYPE) {
3376 tdsLen = TdsEnums.SQL_USHORTVARMAXLEN;
3378 else if (IsVarTimeTds(tdsType))
3379 tdsLen = 0; // placeholder until we read the scale, just make sure it's not SQL_USHORTVARMAXLEN
3380 else if (tdsType == TdsEnums.SQLDATE) {
3381 tdsLen = 3;
3383 else {
3384 if (!TryGetTokenLength(tdsType, stateObj, out tdsLen)) {
3385 return false;
3389 rec.metaType = MetaType.GetSqlDataType(tdsType, userType, tdsLen);
3390 rec.type = rec.metaType.SqlDbType;
3392 // always use the nullable type for parameters if Shiloh or later
3393 // Sphinx sometimes sends fixed length return values
3394 if (_isShiloh) {
3395 rec.tdsType = rec.metaType.NullableType;
3396 rec.isNullable = true;
3397 if (tdsLen == TdsEnums.SQL_USHORTVARMAXLEN) {
3398 Debug.Assert(_isYukon, "plp data from pre-Yukon server");
3399 rec.metaType = MetaType.GetMaxMetaTypeFromMetaType(rec.metaType);
3402 else { // For sphinx, keep the fixed type if that is what is returned
3403 if (rec.metaType.NullableType == tdsType)
3404 rec.isNullable = true;
3406 rec.tdsType = (byte)tdsType;
3409 if (rec.type == SqlDbType.Decimal) {
3410 if (!stateObj.TryReadByte(out rec.precision)) {
3411 return false;
3413 if (!stateObj.TryReadByte(out rec.scale)) {
3414 return false;
3418 if (rec.metaType.IsVarTime) {
3419 if (!stateObj.TryReadByte(out rec.scale)) {
3420 return false;
3424 if (tdsType == TdsEnums.SQLUDT) {
3425 if (!TryProcessUDTMetaData((SqlMetaDataPriv) rec, stateObj)) {
3426 return false;
3430 if (rec.type == SqlDbType.Xml) {
3431 // Read schema info
3432 byte schemapresent;
3433 if (!stateObj.TryReadByte(out schemapresent)) {
3434 return false;
3437 if ((schemapresent & 1) != 0) {
3438 if (!stateObj.TryReadByte(out len)) {
3439 return false;
3441 if (len != 0) {
3442 if (!stateObj.TryReadString(len, out rec.xmlSchemaCollectionDatabase)) {
3443 return false;
3447 if (!stateObj.TryReadByte(out len)) {
3448 return false;
3450 if (len != 0) {
3451 if (!stateObj.TryReadString(len, out rec.xmlSchemaCollectionOwningSchema)) {
3452 return false;
3456 short slen;
3457 if (!stateObj.TryReadInt16(out slen)) {
3458 return false;
3461 if (slen != 0) {
3462 if (!stateObj.TryReadString(slen, out rec.xmlSchemaCollectionName)) {
3463 return false;
3469 else if (_isShiloh && rec.metaType.IsCharType) {
3470 // read the collation for 8.x servers
3471 if (!TryProcessCollation(stateObj, out rec.collation)) {
3472 return false;
3475 int codePage = GetCodePage(rec.collation, stateObj);
3477 // if the column lcid is the same as the default, use the default encoder
3478 if (codePage == _defaultCodePage) {
3479 rec.codePage = _defaultCodePage;
3480 rec.encoding = _defaultEncoding;
3482 else {
3483 rec.codePage = codePage;
3484 rec.encoding = System.Text.Encoding.GetEncoding(rec.codePage);
3488 // For encrypted parameters, read the unencrypted type and encryption information.
3489 if (_serverSupportsColumnEncryption && rec.isEncrypted) {
3490 if (!TryProcessTceCryptoMetadata(stateObj, rec, cipherTable: null, columnEncryptionSetting: columnEncryptionSetting, isReturnValue: true)) {
3491 return false;
3495 // for now we coerce return values into a SQLVariant, not good...
3496 bool isNull = false;
3497 ulong valLen;
3498 if (!TryProcessColumnHeaderNoNBC(rec, stateObj, out isNull, out valLen)) {
3499 return false;
3502 // always read as sql types
3503 Debug.Assert(valLen < (ulong)(Int32.MaxValue), "ProcessReturnValue received data size > 2Gb");
3505 int intlen = valLen > (ulong)(Int32.MaxValue) ? Int32.MaxValue : (int)valLen;
3507 if (rec.metaType.IsPlp) {
3508 intlen = Int32.MaxValue; // If plp data, read it all
3511 if (isNull) {
3512 GetNullSqlValue(rec.value, rec, SqlCommandColumnEncryptionSetting.Disabled, _connHandler);
3514 else {
3515 // We should never do any decryption here, so pass disabled as the command encryption override.
3516 // We only read the binary value and decryption will be performed by OnReturnValue().
3517 if (!TryReadSqlValue(rec.value, rec, intlen, stateObj, SqlCommandColumnEncryptionSetting.Disabled, columnName:null /*Not used*/)) {
3518 return false;
3522 returnValue = rec;
3523 return true;
3526 internal bool TryProcessTceCryptoMetadata (TdsParserStateObject stateObj,
3527 SqlMetaDataPriv col,
3528 SqlTceCipherInfoTable? cipherTable,
3529 SqlCommandColumnEncryptionSetting columnEncryptionSetting,
3530 bool isReturnValue) {
3531 Debug.Assert(isReturnValue == (cipherTable == null), "Ciphertable is not set iff this is a return value");
3533 // Read the ordinal into cipher table
3534 ushort index = 0;
3535 UInt32 userType;
3537 // For return values there is not cipher table and no ordinal.
3538 if (cipherTable.HasValue) {
3539 if (!stateObj.TryReadUInt16(out index)) {
3540 return false;
3543 // validate the index (ordinal passed)
3544 if (index >= cipherTable.Value.Size) {
3545 Bid.Trace("<sc.TdsParser.TryProcessTceCryptoMetadata|TCE> Incorrect ordinal received %d, max tab size: %d\n", index, cipherTable.Value.Size);
3546 throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidOrdinalIntoCipherInfoTable, index);
3550 // Read the user type
3551 if (!stateObj.TryReadUInt32(out userType)) {
3552 return false;
3555 // Read the base TypeInfo
3556 col.baseTI = new SqlMetaDataPriv();
3557 if (!TryProcessTypeInfo(stateObj, col.baseTI, userType)) {
3558 return false;
3561 // Read the cipher algorithm Id
3562 byte cipherAlgorithmId;
3563 if (!stateObj.TryReadByte(out cipherAlgorithmId)) {
3564 return false;
3567 string cipherAlgorithmName = null;
3568 if (TdsEnums.CustomCipherAlgorithmId == cipherAlgorithmId) {
3569 // Custom encryption algorithm, read the name
3570 byte nameSize;
3571 if (!stateObj.TryReadByte(out nameSize)) {
3572 return false;
3575 if (!stateObj.TryReadString(nameSize, out cipherAlgorithmName)) {
3576 return false;
3580 // Read Encryption Type.
3581 byte encryptionType;
3582 if (!stateObj.TryReadByte(out encryptionType)) {
3583 return false;
3586 // Read Normalization Rule Version.
3587 byte normalizationRuleVersion;
3588 if (!stateObj.TryReadByte(out normalizationRuleVersion)) {
3589 return false;
3592 Debug.Assert(col.cipherMD == null, "col.cipherMD should be null in TryProcessTceCryptoMetadata.");
3594 // Check if TCE is enable and if it is set the crypto MD for the column.
3595 // TCE is enabled if the command is set to enabled or to resultset only and this is not a return value
3596 // or if it is set to use connection setting and the connection has TCE enabled.
3597 if ((columnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled ||
3598 (columnEncryptionSetting == SqlCommandColumnEncryptionSetting.ResultSetOnly && !isReturnValue)) ||
3599 (columnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting &&
3600 _connHandler != null && _connHandler.ConnectionOptions != null &&
3601 _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled)) {
3602 col.cipherMD = new SqlCipherMetadata(cipherTable.HasValue ? (SqlTceCipherInfoEntry?)cipherTable.Value[index] : null,
3603 index,
3604 cipherAlgorithmId: cipherAlgorithmId,
3605 cipherAlgorithmName: cipherAlgorithmName,
3606 encryptionType: encryptionType,
3607 normalizationRuleVersion: normalizationRuleVersion);
3609 else {
3610 // If TCE is disabled mark the MD as not encrypted.
3611 col.isEncrypted = false;
3614 return true;
3617 internal bool TryProcessCollation(TdsParserStateObject stateObj, out SqlCollation collation) {
3618 SqlCollation newCollation = new SqlCollation();
3620 if (!stateObj.TryReadUInt32(out newCollation.info)) {
3621 collation = null;
3622 return false;
3624 if (!stateObj.TryReadByte(out newCollation.sortId)) {
3625 collation = null;
3626 return false;
3629 collation = newCollation;
3630 return true;
3633 private void WriteCollation(SqlCollation collation, TdsParserStateObject stateObj) {
3634 if (collation == null) {
3635 _physicalStateObj.WriteByte(0);
3637 else {
3638 _physicalStateObj.WriteByte(sizeof(UInt32)+sizeof(byte));
3639 WriteUnsignedInt(collation.info, _physicalStateObj);
3640 _physicalStateObj.WriteByte(collation.sortId);
3645 internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) {
3646 int codePage = 0;
3648 if (0 != collation.sortId) {
3649 codePage = TdsEnums.CODE_PAGE_FROM_SORT_ID[collation.sortId];
3650 Debug.Assert(0 != codePage, "GetCodePage accessed codepage array and produced 0!, sortID =" + ((Byte)(collation.sortId)).ToString((IFormatProvider)null));
3652 else {
3653 int cultureId = collation.LCID;
3654 bool success = false;
3656 try {
3657 codePage = CultureInfo.GetCultureInfo(cultureId).TextInfo.ANSICodePage;
3659 // SqlHot 50001398: CodePage can be zero, but we should defer such errors until
3660 // we actually MUST use the code page (i.e. don't error if no ANSI data is sent).
3661 success = true;
3663 catch (ArgumentException e) {
3664 ADP.TraceExceptionWithoutRethrow(e);
3667 // If we failed, it is quite possible this is because certain culture id's
3668 // were removed in Win2k and beyond, however Sql Server still supports them.
3669 // There is a workaround for the culture id's listed below, which is to mask
3670 // off the sort id (the leading 1). If that fails, or we have a culture id
3671 // other than the special cases below, we throw an error and throw away the
3672 // rest of the results. For additional info, see MDAC 65963.
3674 // SqlHot 50001398: Sometimes GetCultureInfo will return CodePage 0 instead of throwing.
3675 // treat this as an error also, and switch into the special-case logic.
3676 if (!success || codePage == 0) {
3677 CultureInfo ci = null;
3678 switch (cultureId) {
3679 case 0x10404: // zh-TW
3680 case 0x10804: // zh-CN
3681 case 0x10c04: // zh-HK
3682 case 0x11004: // zh-SG
3683 case 0x11404: // zh-MO
3684 case 0x10411: // ja-JP
3685 case 0x10412: // ko-KR
3686 // If one of the following special cases, mask out sortId and
3687 // retry.
3688 cultureId = cultureId & 0x03fff;
3690 try {
3691 ci = new CultureInfo(cultureId);
3692 success = true;
3694 catch (ArgumentException e) {
3695 ADP.TraceExceptionWithoutRethrow(e);
3697 break;
3698 case 0x827: // Non-supported Lithuanian code page, map it to supported Lithuanian.
3699 try {
3700 ci = new CultureInfo(0x427);
3701 success = true;
3703 catch (ArgumentException e) {
3704 ADP.TraceExceptionWithoutRethrow(e);
3706 break;
3707 default:
3708 break;
3711 // I don't believe we should still be in failure case, but just in case.
3712 if (!success) {
3713 ThrowUnsupportedCollationEncountered(stateObj);
3716 if (null != ci) {
3717 codePage = ci.TextInfo.ANSICodePage;
3722 return codePage;
3726 internal void DrainData(TdsParserStateObject stateObj) {
3727 RuntimeHelpers.PrepareConstrainedRegions();
3728 try {
3729 #if DEBUG
3730 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
3732 RuntimeHelpers.PrepareConstrainedRegions();
3733 try {
3734 tdsReliabilitySection.Start();
3735 #else
3737 #endif //DEBUG
3738 try {
3739 SqlDataReader.SharedState sharedState = stateObj._readerState;
3740 if (sharedState != null && sharedState._dataReady) {
3741 var metadata = stateObj._cleanupMetaData;
3742 if (stateObj._partialHeaderBytesRead > 0) {
3743 if (!stateObj.TryProcessHeader()) {
3744 throw SQL.SynchronousCallMayNotPend();
3747 if (0 == sharedState._nextColumnHeaderToRead) {
3748 // i. user called read but didn't fetch anything
3749 if (!stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj)) {
3750 throw SQL.SynchronousCallMayNotPend();
3753 else {
3754 // iia. if we still have bytes left from a partially read column, skip
3755 if (sharedState._nextColumnDataToRead < sharedState._nextColumnHeaderToRead) {
3756 if ((sharedState._nextColumnHeaderToRead > 0) && (metadata[sharedState._nextColumnHeaderToRead - 1].metaType.IsPlp)) {
3757 if (stateObj._longlen != 0) {
3758 ulong ignored;
3759 if (!TrySkipPlpValue(UInt64.MaxValue, stateObj, out ignored)) {
3760 throw SQL.SynchronousCallMayNotPend();
3765 else if (0 < sharedState._columnDataBytesRemaining) {
3766 if (!stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining)) {
3767 throw SQL.SynchronousCallMayNotPend();
3774 // iib.
3775 // now read the remaining values off the wire for this row
3776 if (!stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj)) {
3777 throw SQL.SynchronousCallMayNotPend();
3782 Run(RunBehavior.Clean, null, null, null, stateObj);
3784 catch {
3785 _connHandler.DoomThisConnection();
3786 throw;
3789 #if DEBUG
3790 finally {
3791 tdsReliabilitySection.Stop();
3793 #endif //DEBUG
3795 catch (System.OutOfMemoryException) {
3796 _connHandler.DoomThisConnection();
3797 throw;
3799 catch (System.StackOverflowException) {
3800 _connHandler.DoomThisConnection();
3801 throw;
3803 catch (System.Threading.ThreadAbortException) {
3804 _connHandler.DoomThisConnection();
3805 throw;
3810 internal void ThrowUnsupportedCollationEncountered(TdsParserStateObject stateObj) {
3811 stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.CultureIdError(), "", 0));
3813 if (null != stateObj) {
3814 DrainData(stateObj);
3816 stateObj._pendingData = false;
3819 ThrowExceptionAndWarning(stateObj);
3824 internal bool TryProcessAltMetaData(int cColumns, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData) {
3825 Debug.Assert(cColumns > 0, "should have at least 1 column in altMetaData!");
3827 metaData = null;
3828 _SqlMetaDataSet altMetaDataSet = new _SqlMetaDataSet(cColumns, null);
3829 int[] indexMap = new int[cColumns];
3831 if (!stateObj.TryReadUInt16(out altMetaDataSet.id)) {
3832 return false;
3835 byte byCols;
3836 if (!stateObj.TryReadByte(out byCols)) {
3837 return false;
3840 while (byCols > 0) {
3841 if (!stateObj.TrySkipBytes(2)) { // ignore ColNum ...
3842 return false;
3844 byCols--;
3847 // pass 1, read the meta data off the wire
3848 for (int i = 0; i < cColumns; i++) {
3849 // internal meta data class
3850 _SqlMetaData col = altMetaDataSet[i];
3852 if (!stateObj.TryReadByte(out col.op)) {
3853 return false;
3855 if (!stateObj.TryReadUInt16(out col.operand)) {
3856 return false;
3859 // TCE is not applicable to AltMetadata.
3860 if (!TryCommonProcessMetaData(stateObj, col, null, fColMD: false, columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Disabled)) {
3861 return false;
3864 if (ADP.IsEmpty(col.column)) {
3865 // create column name from op
3866 switch (col.op) {
3867 case TdsEnums.AOPAVG:
3868 col.column = "avg";
3869 break;
3871 case TdsEnums.AOPCNT:
3872 col.column = "cnt";
3873 break;
3875 case TdsEnums.AOPCNTB:
3876 col.column = "cntb";
3877 break;
3879 case TdsEnums.AOPMAX:
3880 col.column = "max";
3881 break;
3883 case TdsEnums.AOPMIN:
3884 col.column = "min";
3885 break;
3887 case TdsEnums.AOPSUM:
3888 col.column = "sum";
3889 break;
3891 case TdsEnums.AOPANY:
3892 col.column = "any";
3893 break;
3895 case TdsEnums.AOPNOOP:
3896 col.column = "noop";
3897 break;
3899 case TdsEnums.AOPSTDEV:
3900 col.column = "stdev";
3901 break;
3903 case TdsEnums.AOPSTDEVP:
3904 col.column = "stdevp";
3905 break;
3907 case TdsEnums.AOPVAR:
3908 col.column = "var";
3909 break;
3911 case TdsEnums.AOPVARP:
3912 col.column = "varp";
3913 break;
3916 indexMap[i] = i;
3919 altMetaDataSet.indexMap = indexMap;
3920 altMetaDataSet.visibleColumns = cColumns;
3922 metaData = altMetaDataSet;
3923 return true;
3926 /// <summary>
3927 /// <para> Parses the TDS message to read single CIPHER_INFO entry.</para>
3928 /// </summary>
3929 internal bool TryReadCipherInfoEntry (TdsParserStateObject stateObj, out SqlTceCipherInfoEntry entry) {
3930 byte cekValueCount = 0;
3931 entry = new SqlTceCipherInfoEntry(ordinal: 0);
3933 // Read the DB ID
3934 int dbId;
3935 if (!stateObj.TryReadInt32(out dbId)) {
3936 return false;
3939 // Read the keyID
3940 int keyId;
3941 if (!stateObj.TryReadInt32(out keyId)) {
3942 return false;
3945 // Read the key version
3946 int keyVersion;
3947 if (!stateObj.TryReadInt32(out keyVersion)) {
3948 return false;
3951 // Read the key MD Version
3952 byte[] keyMDVersion = new byte[8];
3953 if (!stateObj.TryReadByteArray(keyMDVersion, 0, 8)) {
3954 return false;
3957 // Read the value count
3958 if (!stateObj.TryReadByte (out cekValueCount)) {
3959 return false;
3962 for (int i = 0; i < cekValueCount; i++) {
3963 // Read individual CEK values
3964 byte[] encryptedCek;
3965 string keyPath;
3966 string keyStoreName;
3967 byte algorithmLength;
3968 string algorithmName;
3969 ushort shortValue;
3970 byte byteValue;
3971 int length;
3973 // Read the length of encrypted CEK
3974 if (!stateObj.TryReadUInt16 (out shortValue)) {
3975 return false;
3978 length = shortValue;
3979 encryptedCek = new byte[length];
3981 // Read the actual encrypted CEK
3982 if (!stateObj.TryReadByteArray (encryptedCek, 0, length)) {
3983 return false;
3986 // Read the length of key store name
3987 if (!stateObj.TryReadByte (out byteValue)) {
3988 return false;
3991 length = byteValue;
3993 // And read the key store name now
3994 if (!stateObj.TryReadString(length, out keyStoreName)) {
3995 return false;
3998 // Read the length of key Path
3999 if (!stateObj.TryReadUInt16 (out shortValue)) {
4000 return false;
4003 length = shortValue;
4005 // Read the key path string
4006 if (!stateObj.TryReadString(length, out keyPath)) {
4007 return false;
4010 // Read the length of the string carrying the encryption algo
4011 if (!stateObj.TryReadByte(out algorithmLength)) {
4012 return false;
4015 length = (int)algorithmLength;
4017 // Read the string carrying the encryption algo (eg. RSA_PKCS_OAEP)
4018 if (!stateObj.TryReadString(length, out algorithmName)) {
4019 return false;
4022 // Add this encrypted CEK blob to our list of encrypted values for the CEK
4023 entry.Add(encryptedCek,
4024 databaseId: dbId,
4025 cekId: keyId,
4026 cekVersion: keyVersion,
4027 cekMdVersion: keyMDVersion,
4028 keyPath: keyPath,
4029 keyStoreName: keyStoreName,
4030 algorithmName: algorithmName);
4033 return true;
4036 /// <summary>
4037 /// <para> Parses the TDS message to read a single CIPHER_INFO table.</para>
4038 /// </summary>
4039 internal bool TryProcessCipherInfoTable (TdsParserStateObject stateObj, out SqlTceCipherInfoTable? cipherTable) {
4040 // Read count
4041 short tableSize = 0;
4042 cipherTable = null;
4043 if (!stateObj.TryReadInt16(out tableSize)) {
4044 return false;
4047 if (0 != tableSize) {
4048 SqlTceCipherInfoTable tempTable = new SqlTceCipherInfoTable(tableSize);
4050 // Read individual entries
4051 for (int i = 0; i < tableSize; i++) {
4052 SqlTceCipherInfoEntry entry;
4053 if (!TryReadCipherInfoEntry (stateObj, out entry)) {
4054 return false;
4057 tempTable[i] = entry;
4060 cipherTable = tempTable;
4063 return true;
4066 internal bool TryProcessMetaData(int cColumns, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData, SqlCommandColumnEncryptionSetting columnEncryptionSetting) {
4067 Debug.Assert(cColumns > 0, "should have at least 1 column in metadata!");
4069 // Read the cipher info table first
4070 SqlTceCipherInfoTable? cipherTable = null;
4071 if (_serverSupportsColumnEncryption) {
4072 if (!TryProcessCipherInfoTable (stateObj, out cipherTable)) {
4073 metaData = null;
4074 return false;
4078 // Read the ColumnData fields
4079 _SqlMetaDataSet newMetaData = new _SqlMetaDataSet(cColumns, cipherTable);
4080 for (int i = 0; i < cColumns; i++) {
4081 if (!TryCommonProcessMetaData(stateObj, newMetaData[i], cipherTable, fColMD: true, columnEncryptionSetting: columnEncryptionSetting)) {
4082 metaData = null;
4083 return false;
4087 // DEVNOTE: cipherTable is discarded at this point since its no longer needed.
4088 metaData = newMetaData;
4089 return true;
4092 private bool IsVarTimeTds(byte tdsType) {
4093 return tdsType == TdsEnums.SQLTIME || tdsType == TdsEnums.SQLDATETIME2 || tdsType == TdsEnums.SQLDATETIMEOFFSET;
4096 private bool TryProcessTypeInfo (TdsParserStateObject stateObj, SqlMetaDataPriv col, UInt32 userType) {
4097 byte byteLen;
4098 byte tdsType;
4099 if (!stateObj.TryReadByte(out tdsType)) {
4100 return false;
4103 if (tdsType == TdsEnums.SQLXMLTYPE)
4104 col.length = TdsEnums.SQL_USHORTVARMAXLEN; //Use the same length as other plp datatypes
4105 else if (IsVarTimeTds(tdsType))
4106 col.length = 0; // placeholder until we read the scale, just make sure it's not SQL_USHORTVARMAXLEN
4107 else if (tdsType == TdsEnums.SQLDATE) {
4108 col.length = 3;
4110 else {
4111 if (!TryGetTokenLength(tdsType, stateObj, out col.length)) {
4112 return false;
4116 col.metaType = MetaType.GetSqlDataType(tdsType, userType, col.length);
4117 col.type = col.metaType.SqlDbType;
4119 // If sphinx, do not change to nullable type
4120 if (_isShiloh)
4121 col.tdsType = (col.isNullable ? col.metaType.NullableType : col.metaType.TDSType);
4122 else
4123 col.tdsType = tdsType;
4125 if (_isYukon) {
4126 if (TdsEnums.SQLUDT == tdsType) {
4127 if (!TryProcessUDTMetaData((SqlMetaDataPriv) col, stateObj)) {
4128 return false;
4132 if (col.length == TdsEnums.SQL_USHORTVARMAXLEN) {
4133 Debug.Assert(tdsType == TdsEnums.SQLXMLTYPE ||
4134 tdsType == TdsEnums.SQLBIGVARCHAR ||
4135 tdsType == TdsEnums.SQLBIGVARBINARY ||
4136 tdsType == TdsEnums.SQLNVARCHAR ||
4137 tdsType == TdsEnums.SQLUDT,
4138 "Invalid streaming datatype");
4139 col.metaType = MetaType.GetMaxMetaTypeFromMetaType(col.metaType);
4140 Debug.Assert(col.metaType.IsLong, "Max datatype not IsLong");
4141 col.length = Int32.MaxValue;
4142 if (tdsType == TdsEnums.SQLXMLTYPE) {
4143 byte schemapresent;
4144 if (!stateObj.TryReadByte(out schemapresent)) {
4145 return false;
4148 if ((schemapresent & 1) != 0) {
4149 if (!stateObj.TryReadByte(out byteLen)) {
4150 return false;
4152 if (byteLen != 0) {
4153 if (!stateObj.TryReadString(byteLen, out col.xmlSchemaCollectionDatabase)) {
4154 return false;
4158 if (!stateObj.TryReadByte(out byteLen)) {
4159 return false;
4161 if (byteLen != 0) {
4162 if (!stateObj.TryReadString(byteLen, out col.xmlSchemaCollectionOwningSchema)) {
4163 return false;
4167 short shortLen;
4168 if (!stateObj.TryReadInt16(out shortLen)) {
4169 return false;
4171 if (byteLen != 0) {
4172 if (!stateObj.TryReadString(shortLen, out col.xmlSchemaCollectionName)) {
4173 return false;
4181 if (col.type == SqlDbType.Decimal) {
4182 if (!stateObj.TryReadByte(out col.precision)) {
4183 return false;
4185 if (!stateObj.TryReadByte(out col.scale)) {
4186 return false;
4190 if (col.metaType.IsVarTime) {
4191 if (!stateObj.TryReadByte(out col.scale)) {
4192 return false;
4195 Debug.Assert(0 <= col.scale && col.scale <= 7);
4197 // calculate actual column length here
4199 switch (col.metaType.SqlDbType)
4201 case SqlDbType.Time:
4202 col.length = MetaType.GetTimeSizeFromScale(col.scale);
4203 break;
4204 case SqlDbType.DateTime2:
4205 // Date in number of days (3 bytes) + time
4206 col.length = 3 + MetaType.GetTimeSizeFromScale(col.scale);
4207 break;
4208 case SqlDbType.DateTimeOffset:
4209 // Date in days (3 bytes) + offset in minutes (2 bytes) + time
4210 col.length = 5 + MetaType.GetTimeSizeFromScale(col.scale);
4211 break;
4213 default:
4214 Debug.Assert(false, "Unknown VariableTime type!");
4215 break;
4219 // read the collation for 7.x servers
4220 if (_isShiloh && col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE)) {
4221 if (!TryProcessCollation(stateObj, out col.collation)) {
4222 return false;
4225 int codePage = GetCodePage(col.collation, stateObj);
4227 if (codePage == _defaultCodePage) {
4228 col.codePage = _defaultCodePage;
4229 col.encoding = _defaultEncoding;
4231 else {
4232 col.codePage = codePage;
4233 col.encoding = System.Text.Encoding.GetEncoding(col.codePage);
4237 return true;
4240 private bool TryCommonProcessMetaData(TdsParserStateObject stateObj, _SqlMetaData col, SqlTceCipherInfoTable? cipherTable, bool fColMD, SqlCommandColumnEncryptionSetting columnEncryptionSetting) {
4241 byte byteLen;
4242 UInt32 userType;
4244 // read user type - 4 bytes Yukon, 2 backwards
4245 if (IsYukonOrNewer) {
4246 if (!stateObj.TryReadUInt32(out userType)) {
4247 return false;
4250 else {
4251 ushort userTypeShort;
4252 if (!stateObj.TryReadUInt16(out userTypeShort)) {
4253 return false;
4255 userType = userTypeShort;
4258 // read flags and set appropriate flags in structure
4259 byte flags;
4260 if (!stateObj.TryReadByte(out flags)) {
4261 return false;
4264 col.updatability = (byte)((flags & TdsEnums.Updatability) >> 2);
4265 col.isNullable = (TdsEnums.Nullable == (flags & TdsEnums.Nullable));
4266 col.isIdentity = (TdsEnums.Identity == (flags & TdsEnums.Identity));
4268 // read second byte of column metadata flags
4269 if (!stateObj.TryReadByte(out flags)) {
4270 return false;
4273 col.isColumnSet = (TdsEnums.IsColumnSet == (flags & TdsEnums.IsColumnSet));
4274 if (fColMD && _serverSupportsColumnEncryption) {
4275 col.isEncrypted = (TdsEnums.IsEncrypted == (flags & TdsEnums.IsEncrypted));
4278 // Read TypeInfo
4279 if (!TryProcessTypeInfo (stateObj, col, userType)) {
4280 return false;
4283 // Read tablename if present
4284 if (col.metaType.IsLong && !col.metaType.IsPlp) {
4285 if (_isYukon) {
4286 int unusedLen = 0xFFFF; //We ignore this value
4287 if (!TryProcessOneTable(stateObj, ref unusedLen, out col.multiPartTableName)) {
4288 return false;
4290 } else {
4291 ushort shortLen;
4292 if (!stateObj.TryReadUInt16(out shortLen)) {
4293 return false;
4295 string tableName;
4296 if (!stateObj.TryReadString(shortLen, out tableName)) {
4297 return false;
4299 // with Sql2000 this is returned as an unquoted mix of catalog.owner.table
4300 // all of which may contain "." and unable to parse correctly from the string alone
4301 // example "select * from pubs..[A.B.C.D.E]" AND only when * will contain a image/text/ntext column
4302 // by delay parsing from execute to SqlDataReader.GetSchemaTable to enable more scenarios
4303 col.multiPartTableName = new MultiPartTableName(tableName);
4307 // Read the TCE column cryptoinfo
4308 if (fColMD && _serverSupportsColumnEncryption && col.isEncrypted) {
4309 // If the column is encrypted, we should have a valid cipherTable
4310 if (cipherTable.HasValue && !TryProcessTceCryptoMetadata (stateObj, col, cipherTable.Value, columnEncryptionSetting, isReturnValue: false)) {
4311 return false;
4315 // Read the column name
4316 if (!stateObj.TryReadByte(out byteLen)) {
4317 return false;
4319 if (!stateObj.TryReadString(byteLen, out col.column)) {
4320 return false;
4323 // We get too many DONE COUNTs from the server, causing too meany StatementCompleted event firings.
4324 // We only need to fire this event when we actually have a meta data stream with 0 or more rows.
4325 stateObj._receivedColMetaData = true;
4326 return true;
4329 private bool TryProcessUDTMetaData(SqlMetaDataPriv metaData, TdsParserStateObject stateObj) {
4330 ushort shortLength;
4331 byte byteLength;
4333 if (!stateObj.TryReadUInt16(out shortLength)) { // max byte size
4334 return false;
4336 metaData.length = shortLength;
4338 // database name
4339 if (!stateObj.TryReadByte(out byteLength)) {
4340 return false;
4342 if (byteLength != 0) {
4343 if (!stateObj.TryReadString(byteLength, out metaData.udtDatabaseName)) {
4344 return false;
4348 // schema name
4349 if (!stateObj.TryReadByte(out byteLength)) {
4350 return false;
4352 if (byteLength != 0) {
4353 if (!stateObj.TryReadString(byteLength, out metaData.udtSchemaName)) {
4354 return false;
4358 // type name
4359 if (!stateObj.TryReadByte(out byteLength)) {
4360 return false;
4362 if (byteLength != 0) {
4363 if (!stateObj.TryReadString(byteLength, out metaData.udtTypeName)) {
4364 return false;
4368 if (!stateObj.TryReadUInt16(out shortLength)) {
4369 return false;
4371 if (shortLength != 0) {
4372 if (!stateObj.TryReadString(shortLength, out metaData.udtAssemblyQualifiedName)) {
4373 return false;
4377 return true;
4380 private void WriteUDTMetaData(object value, string database, string schema, string type,
4381 TdsParserStateObject stateObj) {
4382 // database
4383 if (ADP.IsEmpty(database)) {
4384 stateObj.WriteByte(0);
4386 else {
4387 stateObj.WriteByte((byte)database.Length);
4388 WriteString(database, stateObj);
4391 // schema
4392 if (ADP.IsEmpty(schema)) {
4393 stateObj.WriteByte(0);
4395 else {
4396 stateObj.WriteByte((byte)schema.Length);
4397 WriteString(schema, stateObj);
4400 // type
4401 if (ADP.IsEmpty(type)) {
4402 stateObj.WriteByte(0);
4404 else {
4405 stateObj.WriteByte((byte)type.Length);
4406 WriteString(type, stateObj);
4410 internal bool TryProcessTableName(int length, TdsParserStateObject stateObj, out MultiPartTableName[] multiPartTableNames) {
4411 int tablesAdded = 0;
4413 MultiPartTableName[] tables = new MultiPartTableName[1];
4414 MultiPartTableName mpt;
4415 while (length > 0) {
4419 if (!TryProcessOneTable(stateObj, ref length, out mpt)) {
4420 multiPartTableNames = null;
4421 return false;
4423 if (tablesAdded == 0) {
4424 tables[tablesAdded] = mpt;
4426 else {
4427 MultiPartTableName[] newTables = new MultiPartTableName[tables.Length + 1];
4428 Array.Copy(tables, 0, newTables, 0, tables.Length);
4429 newTables[tables.Length] = mpt;
4430 tables = newTables;
4433 tablesAdded++;
4436 multiPartTableNames = tables;
4437 return true;
4440 private bool TryProcessOneTable(TdsParserStateObject stateObj, ref int length, out MultiPartTableName multiPartTableName) {
4441 ushort tableLen;
4442 MultiPartTableName mpt;
4443 string value;
4445 multiPartTableName = default(MultiPartTableName);
4447 if (_isShilohSP1) {
4449 mpt = new MultiPartTableName();
4450 byte nParts;
4452 // Find out how many parts in the TDS stream
4453 if (!stateObj.TryReadByte(out nParts)) {
4454 return false;
4456 length--;
4457 if (nParts == 4) {
4458 if (!stateObj.TryReadUInt16(out tableLen)) {
4459 return false;
4461 length -= 2;
4462 if (!stateObj.TryReadString(tableLen, out value)) {
4463 return false;
4465 mpt.ServerName = value;
4466 nParts--;
4467 length -= (tableLen * 2); // wide bytes
4469 if (nParts == 3) {
4470 if (!stateObj.TryReadUInt16(out tableLen)) {
4471 return false;
4473 length -= 2;
4474 if (!stateObj.TryReadString(tableLen, out value)) {
4475 return false;
4477 mpt.CatalogName = value;
4478 length -= (tableLen * 2); // wide bytes
4479 nParts--;
4481 if (nParts == 2) {
4482 if (!stateObj.TryReadUInt16(out tableLen)) {
4483 return false;
4485 length -= 2;
4486 if (!stateObj.TryReadString(tableLen, out value)) {
4487 return false;
4489 mpt.SchemaName = value;
4490 length -= (tableLen * 2); // wide bytes
4491 nParts--;
4493 if (nParts == 1) {
4494 if (!stateObj.TryReadUInt16(out tableLen)) {
4495 return false;
4497 length -= 2;
4498 if (!stateObj.TryReadString(tableLen, out value)) {
4499 return false;
4501 mpt.TableName = value;
4502 length -= (tableLen * 2); // wide bytes
4503 nParts--;
4505 Debug.Assert(nParts == 0 , "ProcessTableName:Unidentified parts in the table name token stream!");
4508 else {
4509 if (!stateObj.TryReadUInt16(out tableLen)) {
4510 return false;
4512 length -= 2;
4513 if (!stateObj.TryReadString(tableLen, out value)) {
4514 return false;
4516 string tableName = value;
4517 length -= (tableLen * 2); // wide bytes
4518 mpt = new MultiPartTableName(MultipartIdentifier.ParseMultipartIdentifier(tableName, "[\"", "]\"", Res.SQL_TDSParserTableName, false));
4521 multiPartTableName = mpt;
4522 return true;
4525 // augments current metadata with table and key information
4526 private bool TryProcessColInfo(_SqlMetaDataSet columns, SqlDataReader reader, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData) {
4527 Debug.Assert(columns != null && columns.Length > 0, "no metadata available!");
4529 metaData = null;
4531 for (int i = 0; i < columns.Length; i++) {
4532 _SqlMetaData col = columns[i];
4534 byte ignored;
4535 if (!stateObj.TryReadByte(out ignored)) { // colnum, ignore
4536 return false;
4538 if (!stateObj.TryReadByte(out col.tableNum)) {
4539 return false;
4542 // interpret status
4543 byte status;
4544 if (!stateObj.TryReadByte(out status)) {
4545 return false;
4548 col.isDifferentName = (TdsEnums.SQLDifferentName == (status & TdsEnums.SQLDifferentName));
4549 col.isExpression = (TdsEnums.SQLExpression == (status & TdsEnums.SQLExpression));
4550 col.isKey = (TdsEnums.SQLKey == (status & TdsEnums.SQLKey));
4551 col.isHidden = (TdsEnums.SQLHidden == (status & TdsEnums.SQLHidden));
4553 // read off the base table name if it is different than the select list column name
4554 if (col.isDifferentName) {
4555 byte len;
4556 if (!stateObj.TryReadByte(out len)) {
4557 return false;
4559 if (!stateObj.TryReadString(len, out col.baseColumn)) {
4560 return false;
4564 // Fixup column name - only if result of a table - that is if it was not the result of
4565 // an expression.
4566 if ((reader.TableNames != null) && (col.tableNum > 0)) {
4567 Debug.Assert(reader.TableNames.Length >= col.tableNum, "invalid tableNames array!");
4568 col.multiPartTableName = reader.TableNames[col.tableNum - 1];
4571 // MDAC 60109: expressions are readonly
4572 if (col.isExpression) {
4573 col.updatability = 0;
4577 // set the metadata so that the stream knows some metadata info has changed
4578 metaData = columns;
4579 return true;
4582 // takes care of any per data header information:
4583 // for long columns, reads off textptrs, reads length, check nullability
4584 // for other columns, reads length, checks nullability
4585 // returns length and nullability
4586 internal bool TryProcessColumnHeader(SqlMetaDataPriv col, TdsParserStateObject stateObj, int columnOrdinal, out bool isNull, out ulong length) {
4587 // query NBC row information first
4588 if (stateObj.IsNullCompressionBitSet(columnOrdinal)) {
4589 isNull = true;
4590 // column information is not present in TDS if null compression bit is set, return now
4591 length = 0;
4592 return true;
4595 return TryProcessColumnHeaderNoNBC(col, stateObj, out isNull, out length);
4598 private bool TryProcessColumnHeaderNoNBC(SqlMetaDataPriv col, TdsParserStateObject stateObj, out bool isNull, out ulong length) {
4599 if (col.metaType.IsLong && !col.metaType.IsPlp) {
4601 // we don't care about TextPtrs, simply go after the data after it
4603 byte textPtrLen;
4604 if (!stateObj.TryReadByte(out textPtrLen)) {
4605 isNull = false;
4606 length = 0;
4607 return false;
4610 if (0 != textPtrLen) {
4611 // read past text pointer
4612 if (!stateObj.TrySkipBytes(textPtrLen)) {
4613 isNull = false;
4614 length = 0;
4615 return false;
4618 // read past timestamp
4619 if (!stateObj.TrySkipBytes(TdsEnums.TEXT_TIME_STAMP_LEN)) {
4620 isNull = false;
4621 length = 0;
4622 return false;
4625 isNull = false;
4626 return TryGetDataLength(col, stateObj, out length);
4628 else {
4629 isNull = true;
4630 length = 0;
4631 return true;
4635 else {
4636 // non-blob columns
4637 ulong longlen;
4638 if (!TryGetDataLength(col, stateObj, out longlen)) {
4639 isNull = false;
4640 length = 0;
4641 return false;
4643 isNull = IsNull(col.metaType, longlen);
4644 length = (isNull ? 0 : longlen);
4645 return true;
4649 // assumes that the current position is at the start of an altrow!
4650 internal bool TryGetAltRowId(TdsParserStateObject stateObj, out int id) {
4651 byte token;
4652 if (!stateObj.TryReadByte(out token)) { // skip over ALTROW token
4653 id = 0;
4654 return false;
4656 Debug.Assert((token == TdsEnums.SQLALTROW), "");
4658 // Start a fresh row - disable NBC since Alt Rows are never compressed
4659 if (!stateObj.TryStartNewRow(isNullCompressed: false)) {
4660 id = 0;
4661 return false;
4664 ushort shortId;
4665 if (!stateObj.TryReadUInt16(out shortId)) {
4666 id = 0;
4667 return false;
4670 id = shortId;
4671 return true;
4674 // Used internally by BulkCopy only
4675 private bool TryProcessRow(_SqlMetaDataSet columns, object[] buffer, int[] map, TdsParserStateObject stateObj) {
4676 SqlBuffer data = new SqlBuffer();
4678 for (int i = 0; i < columns.Length; i++) {
4679 _SqlMetaData md = columns[i];
4680 Debug.Assert(md != null, "_SqlMetaData should not be null for column " + i.ToString(CultureInfo.InvariantCulture));
4682 bool isNull;
4683 ulong len;
4684 if (!TryProcessColumnHeader(md, stateObj, i, out isNull, out len)) {
4685 return false;
4688 if (isNull) {
4689 GetNullSqlValue(data, md, SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/, _connHandler);
4690 buffer[map[i]] = data.SqlValue;
4692 else {
4693 // We only read up to 2Gb. Throw if data is larger. Very large data
4694 // should be read in chunks in sequential read mode
4695 // For Plp columns, we may have gotten only the length of the first chunk
4696 if (!TryReadSqlValue(data, md, md.metaType.IsPlp ? (Int32.MaxValue) : (int)len, stateObj,
4697 SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/,
4698 md.column)) {
4699 return false;
4701 buffer[map[i]] = data.SqlValue;
4702 if (stateObj._longlen != 0) {
4703 throw new SqlTruncateException(Res.GetString(Res.SqlMisc_TruncationMaxDataMessage));
4706 data.Clear();
4709 return true;
4712 /// <summary>
4713 /// Determines if a column value should be transparently decrypted (based on SqlCommand and Connection String settings).
4714 /// </summary>
4715 /// <returns>true if the value should be transparently decrypted, false otherwise</returns>
4716 internal static bool ShouldHonorTceForRead (SqlCommandColumnEncryptionSetting columnEncryptionSetting,
4717 SqlInternalConnectionTds connection) {
4719 // Command leve setting trumps all
4720 switch (columnEncryptionSetting) {
4721 case SqlCommandColumnEncryptionSetting.Disabled:
4722 return false;
4723 case SqlCommandColumnEncryptionSetting.Enabled:
4724 return true;
4725 case SqlCommandColumnEncryptionSetting.ResultSetOnly:
4726 return true;
4727 default:
4728 // Check connection level setting!
4729 Debug.Assert(SqlCommandColumnEncryptionSetting.UseConnectionSetting == columnEncryptionSetting,
4730 "Unexpected value for command level override");
4731 return (connection != null && connection.ConnectionOptions != null &&
4732 connection.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled);
4736 internal static object GetNullSqlValue(
4737 SqlBuffer nullVal,
4738 SqlMetaDataPriv md,
4739 SqlCommandColumnEncryptionSetting columnEncryptionSetting,
4740 SqlInternalConnectionTds connection) {
4741 SqlDbType type = md.type;
4743 if (type == SqlDbType.VarBinary && // if its a varbinary
4744 md.isEncrypted &&// and encrypted
4745 ShouldHonorTceForRead(columnEncryptionSetting, connection)){
4746 type = md.baseTI.type; // the use the actual (plaintext) type
4749 switch (type) {
4750 case SqlDbType.Real:
4751 nullVal.SetToNullOfType(SqlBuffer.StorageType.Single);
4752 break;
4754 case SqlDbType.Float:
4755 nullVal.SetToNullOfType(SqlBuffer.StorageType.Double);
4756 break;
4758 case SqlDbType.Udt:
4759 case SqlDbType.Binary:
4760 case SqlDbType.VarBinary:
4761 case SqlDbType.Image:
4762 nullVal.SqlBinary = SqlBinary.Null;
4763 break;
4765 case SqlDbType.UniqueIdentifier:
4766 nullVal.SqlGuid = SqlGuid.Null;
4767 break;
4769 case SqlDbType.Bit:
4770 nullVal.SetToNullOfType(SqlBuffer.StorageType.Boolean);
4771 break;
4773 case SqlDbType.TinyInt:
4774 nullVal.SetToNullOfType(SqlBuffer.StorageType.Byte);
4775 break;
4777 case SqlDbType.SmallInt:
4778 nullVal.SetToNullOfType(SqlBuffer.StorageType.Int16);
4779 break;
4781 case SqlDbType.Int:
4782 nullVal.SetToNullOfType(SqlBuffer.StorageType.Int32);
4783 break;
4785 case SqlDbType.BigInt:
4786 nullVal.SetToNullOfType(SqlBuffer.StorageType.Int64);
4787 break;
4789 case SqlDbType.Char:
4790 case SqlDbType.VarChar:
4791 case SqlDbType.NChar:
4792 case SqlDbType.NVarChar:
4793 case SqlDbType.Text:
4794 case SqlDbType.NText:
4795 nullVal.SetToNullOfType(SqlBuffer.StorageType.String);
4796 break;
4798 case SqlDbType.Decimal:
4799 nullVal.SetToNullOfType(SqlBuffer.StorageType.Decimal);
4800 break;
4802 case SqlDbType.DateTime:
4803 case SqlDbType.SmallDateTime:
4804 nullVal.SetToNullOfType(SqlBuffer.StorageType.DateTime);
4805 break;
4807 case SqlDbType.Money:
4808 case SqlDbType.SmallMoney:
4809 nullVal.SetToNullOfType(SqlBuffer.StorageType.Money);
4810 break;
4812 case SqlDbType.Variant:
4813 // DBNull.Value will have to work here
4814 nullVal.SetToNullOfType(SqlBuffer.StorageType.Empty);
4815 break;
4817 case SqlDbType.Xml:
4818 nullVal.SqlCachedBuffer = SqlCachedBuffer.Null;
4819 break;
4821 case SqlDbType.Date:
4822 nullVal.SetToNullOfType(SqlBuffer.StorageType.Date);
4823 break;
4825 case SqlDbType.Time:
4826 nullVal.SetToNullOfType(SqlBuffer.StorageType.Time);
4827 break;
4829 case SqlDbType.DateTime2:
4830 nullVal.SetToNullOfType(SqlBuffer.StorageType.DateTime2);
4831 break;
4833 case SqlDbType.DateTimeOffset:
4834 nullVal.SetToNullOfType(SqlBuffer.StorageType.DateTimeOffset);
4835 break;
4837 case SqlDbType.Timestamp:
4838 // Dev10 Bug #479607 - this should have been the same as SqlDbType.Binary, but it's a rejected breaking change
4839 // Dev10 Bug #752790 - don't assert when it does happen
4840 break;
4842 default:
4843 Debug.Assert(false, "unknown null sqlType!" + md.type.ToString());
4844 break;
4847 return nullVal;
4850 internal bool TrySkipRow(_SqlMetaDataSet columns, TdsParserStateObject stateObj) {
4851 return TrySkipRow(columns, 0, stateObj);
4854 internal bool TrySkipRow(_SqlMetaDataSet columns, int startCol, TdsParserStateObject stateObj) {
4855 for (int i = startCol; i < columns.Length; i++) {
4856 _SqlMetaData md = columns[i];
4858 if (!TrySkipValue(md, i, stateObj)) {
4859 return false;
4862 return true;
4865 /// <summary>
4866 /// 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
4867 /// </summary>
4868 internal bool TrySkipValue(SqlMetaDataPriv md, int columnOrdinal, TdsParserStateObject stateObj) {
4869 if (stateObj.IsNullCompressionBitSet(columnOrdinal)) {
4870 return true;
4873 if (md.metaType.IsPlp) {
4874 ulong ignored;
4875 if (!TrySkipPlpValue(UInt64.MaxValue, stateObj, out ignored)) {
4876 return false;
4879 else if (md.metaType.IsLong) {
4881 Debug.Assert(!md.metaType.IsPlp, "Plp types must be handled using SkipPlpValue");
4883 byte textPtrLen;
4884 if (!stateObj.TryReadByte(out textPtrLen)) {
4885 return false;
4888 if (0 != textPtrLen) {
4889 if (!stateObj.TrySkipBytes(textPtrLen + TdsEnums.TEXT_TIME_STAMP_LEN)) {
4890 return false;
4893 int length;
4894 if (!TryGetTokenLength(md.tdsType, stateObj, out length)) {
4895 return false;
4897 if (!stateObj.TrySkipBytes(length)) {
4898 return false;
4902 else {
4903 int length;
4904 if (!TryGetTokenLength(md.tdsType, stateObj, out length)) {
4905 return false;
4908 // if false, no value to skip - it's null
4909 if (!IsNull(md.metaType, (ulong)length)) {
4910 if (!stateObj.TrySkipBytes(length)) {
4911 return false;
4916 return true;
4919 private bool IsNull(MetaType mt, ulong length) {
4920 // null bin and char types have a length of -1 to represent null
4921 if (mt.IsPlp) {
4922 return (TdsEnums.SQL_PLP_NULL == length);
4925 // HOTFIX #50000415: for image/text, 0xFFFF is the length, not representing null
4926 if ((TdsEnums.VARNULL == length) && !mt.IsLong) {
4927 return true;
4930 // other types have a length of 0 to represent null
4931 // long and non-PLP types will always return false because these types are either char or binary
4932 // this is expected since for long and non-plp types isnull is checked based on textptr field and not the length
4933 return ((TdsEnums.FIXEDNULL == length) && !mt.IsCharType && !mt.IsBinType);
4936 private bool TryReadSqlStringValue(SqlBuffer value, byte type, int length, Encoding encoding, bool isPlp, TdsParserStateObject stateObj) {
4937 switch (type) {
4938 case TdsEnums.SQLCHAR:
4939 case TdsEnums.SQLBIGCHAR:
4940 case TdsEnums.SQLVARCHAR:
4941 case TdsEnums.SQLBIGVARCHAR:
4942 case TdsEnums.SQLTEXT:
4943 // If bigvarchar(max), we only read the first chunk here,
4944 // expecting the caller to read the rest
4945 if (encoding == null) {
4946 // if hitting 7.0 server, encoding will be null in metadata for columns or return values since
4947 // 7.0 has no support for multiple code pages in data - single code page support only
4948 encoding = _defaultEncoding;
4950 string stringValue;
4951 if (!stateObj.TryReadStringWithEncoding(length, encoding, isPlp, out stringValue)) {
4952 return false;
4954 value.SetToString(stringValue);
4955 break;
4957 case TdsEnums.SQLNCHAR:
4958 case TdsEnums.SQLNVARCHAR:
4959 case TdsEnums.SQLNTEXT:
4961 String s = null;
4963 if (isPlp) {
4964 char[] cc = null;
4966 if (!TryReadPlpUnicodeChars(ref cc, 0, length >> 1, stateObj, out length)) {
4967 return false;
4969 if (length > 0) {
4970 s = new String(cc, 0, length);
4972 else {
4973 s = ADP.StrEmpty;
4976 else {
4977 if (!stateObj.TryReadString(length >> 1, out s)) {
4978 return false;
4982 value.SetToString(s);
4983 break;
4986 default:
4987 Debug.Assert(false, "Unknown tds type for SqlString!" + type.ToString(CultureInfo.InvariantCulture));
4988 break;
4991 return true;
4994 /// <summary>
4995 /// Deserializes the unencrypted bytes into a value based on the target type info.
4996 /// </summary>
4997 internal bool DeserializeUnencryptedValue (SqlBuffer value, byte[] unencryptedBytes, SqlMetaDataPriv md, TdsParserStateObject stateObj, byte normalizationVersion) {
4998 if (normalizationVersion != 0x01) {
4999 throw SQL.UnsupportedNormalizationVersion(normalizationVersion);
5002 byte tdsType = md.baseTI.tdsType;
5003 int length = unencryptedBytes.Length;
5005 // For normalized types, the length and scale of the actual type might be different than the value's.
5006 int denormalizedLength = md.baseTI.length;
5007 byte denormalizedScale = md.baseTI.scale;
5009 Debug.Assert (false == md.baseTI.isEncrypted, "Double encryption detected");
5010 switch (tdsType) {
5011 // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT.
5012 case TdsEnums.SQLBIT:
5013 case TdsEnums.SQLBITN:
5014 case TdsEnums.SQLINTN:
5015 case TdsEnums.SQLINT1:
5016 case TdsEnums.SQLINT2:
5017 case TdsEnums.SQLINT4:
5018 case TdsEnums.SQLINT8:
5019 Debug.Assert(length == 8, "invalid length for SqlInt64 type!");
5020 byte byteValue;
5021 long longValue;
5023 if (unencryptedBytes.Length != 8) {
5024 return false;
5027 longValue = BitConverter.ToInt64(unencryptedBytes, 0);
5029 if (tdsType == TdsEnums.SQLBIT ||
5030 tdsType == TdsEnums.SQLBITN) {
5031 value.Boolean = (longValue != 0);
5032 break;
5035 if (tdsType == TdsEnums.SQLINT1 || denormalizedLength == 1)
5036 value.Byte = (byte)longValue;
5037 else if (tdsType == TdsEnums.SQLINT2 || denormalizedLength == 2)
5038 value.Int16 = (Int16)longValue;
5039 else if (tdsType == TdsEnums.SQLINT4 || denormalizedLength == 4)
5040 value.Int32 = (Int32)longValue;
5041 else
5042 value.Int64 = longValue;
5044 break;
5046 case TdsEnums.SQLFLTN:
5047 if (length == 4) {
5048 goto case TdsEnums.SQLFLT4;
5050 else {
5051 goto case TdsEnums.SQLFLT8;
5054 case TdsEnums.SQLFLT4:
5055 Debug.Assert(length == 4, "invalid length for SqlSingle type!");
5056 float singleValue;
5057 if (unencryptedBytes.Length != 4) {
5058 return false;
5061 singleValue = BitConverter.ToSingle(unencryptedBytes, 0);
5062 value.Single = singleValue;
5063 break;
5065 case TdsEnums.SQLFLT8:
5066 double doubleValue;
5067 if (unencryptedBytes.Length != 8) {
5068 return false;
5071 doubleValue = BitConverter.ToDouble(unencryptedBytes, 0);
5072 value.Double = doubleValue;
5073 break;
5075 // We normalize to allow conversion across data types. SMALLMONEY is serialized into a MONEY.
5076 case TdsEnums.SQLMONEYN:
5077 case TdsEnums.SQLMONEY4:
5078 case TdsEnums.SQLMONEY:
5080 int mid;
5081 uint lo;
5083 if (unencryptedBytes.Length != 8) {
5084 return false;
5087 mid = BitConverter.ToInt32(unencryptedBytes, 0);
5088 lo = BitConverter.ToUInt32(unencryptedBytes, 4);
5090 long l = (((long)mid) << 0x20) + ((long)lo);
5091 value.SetToMoney(l);
5092 break;
5095 case TdsEnums.SQLDATETIMN:
5096 if (length == 4) {
5097 goto case TdsEnums.SQLDATETIM4;
5099 else {
5100 goto case TdsEnums.SQLDATETIME;
5103 case TdsEnums.SQLDATETIM4:
5104 ushort daypartShort, timepartShort;
5105 if (unencryptedBytes.Length != 4) {
5106 return false;
5109 daypartShort = (UInt16)((unencryptedBytes[1] << 8) + unencryptedBytes[0]);
5110 timepartShort = (UInt16)((unencryptedBytes[3] << 8) + unencryptedBytes[2]);
5111 value.SetToDateTime(daypartShort, timepartShort * SqlDateTime.SQLTicksPerMinute);
5112 break;
5114 case TdsEnums.SQLDATETIME:
5115 int daypart;
5116 uint timepart;
5117 if (unencryptedBytes.Length != 8) {
5118 return false;
5121 daypart = BitConverter.ToInt32(unencryptedBytes, 0);
5122 timepart = BitConverter.ToUInt32(unencryptedBytes, 4);
5123 value.SetToDateTime(daypart, (int)timepart);
5124 break;
5126 case TdsEnums.SQLUNIQUEID:
5128 Debug.Assert(length == 16, "invalid length for SqlGuid type!");
5129 value.SqlGuid = new SqlGuid(unencryptedBytes, true); // doesn't copy the byte array
5130 break;
5133 case TdsEnums.SQLBINARY:
5134 case TdsEnums.SQLBIGBINARY:
5135 case TdsEnums.SQLBIGVARBINARY:
5136 case TdsEnums.SQLVARBINARY:
5137 case TdsEnums.SQLIMAGE:
5139 // Note: Better not come here with plp data!!
5140 Debug.Assert(length <= TdsEnums.MAXSIZE, "Plp data decryption attempted");
5142 // If this is a fixed length type, pad with zeros to get to the fixed length size.
5143 if (tdsType == TdsEnums.SQLBINARY || tdsType == TdsEnums.SQLBIGBINARY) {
5144 byte[] bytes = new byte[md.baseTI.length];
5145 Buffer.BlockCopy(unencryptedBytes, 0, bytes, 0, unencryptedBytes.Length);
5146 unencryptedBytes = bytes;
5149 value.SqlBinary = new SqlBinary(unencryptedBytes, true); // doesn't copy the byte array
5150 break;
5153 case TdsEnums.SQLDECIMALN:
5154 case TdsEnums.SQLNUMERICN:
5155 // Check the sign from the first byte.
5156 int index = 0;
5157 byteValue = unencryptedBytes[index++];
5158 bool fPositive = (1 == byteValue);
5160 // Now read the 4 next integers which contain the actual value.
5161 length = checked((int)length-1);
5162 int[] bits = new int[4];
5163 int decLength = length>>2;
5164 for (int i = 0; i < decLength; i++) {
5165 // up to 16 bytes of data following the sign byte
5166 bits[i] = BitConverter.ToInt32(unencryptedBytes, index);
5167 index += 4;
5169 value.SetToDecimal (md.baseTI.precision, md.baseTI.scale, fPositive, bits);
5170 break;
5172 case TdsEnums.SQLCHAR:
5173 case TdsEnums.SQLBIGCHAR:
5174 case TdsEnums.SQLVARCHAR:
5175 case TdsEnums.SQLBIGVARCHAR:
5176 case TdsEnums.SQLTEXT:
5178 System.Text.Encoding encoding = md.baseTI.encoding;
5180 if (null == encoding) {
5181 encoding = _defaultEncoding;
5184 if (null == encoding) {
5185 ThrowUnsupportedCollationEncountered(stateObj);
5188 string strValue = encoding.GetString(unencryptedBytes, 0, length);
5190 // If this is a fixed length type, pad with spaces to get to the fixed length size.
5191 if (tdsType == TdsEnums.SQLCHAR || tdsType == TdsEnums.SQLBIGCHAR) {
5192 strValue = strValue.PadRight(md.baseTI.length);
5195 value.SetToString(strValue);
5196 break;
5199 case TdsEnums.SQLNCHAR:
5200 case TdsEnums.SQLNVARCHAR:
5201 case TdsEnums.SQLNTEXT:
5203 string strValue = System.Text.Encoding.Unicode.GetString(unencryptedBytes, 0, length);
5205 // If this is a fixed length type, pad with spaces to get to the fixed length size.
5206 if (tdsType == TdsEnums.SQLNCHAR) {
5207 strValue = strValue.PadRight(md.baseTI.length / ADP.CharSize);
5210 value.SetToString(strValue);
5211 break;
5214 case TdsEnums.SQLDATE:
5215 Debug.Assert(length == 3, "invalid length for date type!");
5216 value.SetToDate(unencryptedBytes);
5217 break;
5219 case TdsEnums.SQLTIME:
5220 // We normalize to maximum precision to allow conversion across different precisions.
5221 Debug.Assert(length == 5, "invalid length for time type!");
5222 value.SetToTime(unencryptedBytes, length, TdsEnums.MAX_TIME_SCALE, denormalizedScale);
5223 break;
5225 case TdsEnums.SQLDATETIME2:
5226 // We normalize to maximum precision to allow conversion across different precisions.
5227 Debug.Assert(length == 8, "invalid length for datetime2 type!");
5228 value.SetToDateTime2(unencryptedBytes, length, TdsEnums.MAX_TIME_SCALE, denormalizedScale);
5229 break;
5231 case TdsEnums.SQLDATETIMEOFFSET:
5232 // We normalize to maximum precision to allow conversion across different precisions.
5233 Debug.Assert(length == 10, "invalid length for datetimeoffset type!");
5234 value.SetToDateTimeOffset(unencryptedBytes, length, TdsEnums.MAX_TIME_SCALE, denormalizedScale);
5235 break;
5237 default:
5238 MetaType metaType = md.baseTI.metaType;
5240 // If we don't have a metatype already, construct one to get the proper type name.
5241 if (metaType == null) {
5242 metaType = MetaType.GetSqlDataType(tdsType, userType:0, length:length);
5245 throw SQL.UnsupportedDatatypeEncryption(metaType.TypeName);
5248 return true;
5251 internal bool TryReadSqlValue(SqlBuffer value,
5252 SqlMetaDataPriv md,
5253 int length,
5254 TdsParserStateObject stateObj,
5255 SqlCommandColumnEncryptionSetting columnEncryptionOverride,
5256 string columnName) {
5257 bool isPlp = md.metaType.IsPlp;
5258 byte tdsType = md.tdsType;
5260 Debug.Assert(isPlp || !IsNull(md.metaType, (ulong)length), "null value should not get here!");
5261 if (isPlp) {
5262 // We must read the column value completely, no matter what length is passed in
5263 length = Int32.MaxValue;
5266 //DEVNOTE: When modifying the following routines (for deserialization) please pay attention to
5267 // deserialization code in DecryptWithKey () method and modify it accordingly.
5268 switch (tdsType) {
5269 case TdsEnums.SQLDECIMALN:
5270 case TdsEnums.SQLNUMERICN:
5271 if (!TryReadSqlDecimal(value, length, md.precision, md.scale, stateObj)) {
5272 return false;
5274 break;
5276 case TdsEnums.SQLUDT:
5277 case TdsEnums.SQLBINARY:
5278 case TdsEnums.SQLBIGBINARY:
5279 case TdsEnums.SQLBIGVARBINARY:
5280 case TdsEnums.SQLVARBINARY:
5281 case TdsEnums.SQLIMAGE:
5282 byte[] b = null;
5284 // If varbinary(max), we only read the first chunk here, expecting the caller to read the rest
5285 if (isPlp) {
5286 // If we are given -1 for length, then we read the entire value,
5287 // otherwise only the requested amount, usually first chunk.
5288 int ignored;
5289 if (!stateObj.TryReadPlpBytes(ref b, 0, length, out ignored)) {
5290 return false;
5293 else {
5294 //Debug.Assert(length > 0 && length < (long)(Int32.MaxValue), "Bad length for column");
5295 b = new byte[length];
5296 if (!stateObj.TryReadByteArray(b, 0, length)) {
5297 return false;
5301 if (md.isEncrypted
5302 && ((columnEncryptionOverride == SqlCommandColumnEncryptionSetting.Enabled
5303 || columnEncryptionOverride == SqlCommandColumnEncryptionSetting.ResultSetOnly)
5304 || (columnEncryptionOverride == SqlCommandColumnEncryptionSetting.UseConnectionSetting
5305 && _connHandler != null && _connHandler.ConnectionOptions != null
5306 && _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled))) {
5307 try {
5308 // CipherInfo is present, decrypt and read
5309 byte[] unencryptedBytes = SqlSecurityUtility.DecryptWithKey(b, md.cipherMD, _connHandler.ConnectionOptions.DataSource);
5311 if (unencryptedBytes != null) {
5312 DeserializeUnencryptedValue(value, unencryptedBytes, md, stateObj, md.NormalizationRuleVersion);
5315 catch (Exception e) {
5316 throw SQL.ColumnDecryptionFailed(columnName, null, e);
5319 else {
5320 value.SqlBinary = new SqlBinary(b, true); // doesn't copy the byte array
5322 break;
5324 case TdsEnums.SQLCHAR:
5325 case TdsEnums.SQLBIGCHAR:
5326 case TdsEnums.SQLVARCHAR:
5327 case TdsEnums.SQLBIGVARCHAR:
5328 case TdsEnums.SQLTEXT:
5329 case TdsEnums.SQLNCHAR:
5330 case TdsEnums.SQLNVARCHAR:
5331 case TdsEnums.SQLNTEXT:
5332 if (!TryReadSqlStringValue(value, tdsType, length, md.encoding, isPlp, stateObj)) {
5333 return false;
5335 break;
5337 case TdsEnums.SQLXMLTYPE:
5338 // We store SqlCachedBuffer here, so that we can return either SqlBinary, SqlString or SqlXmlReader.
5339 SqlCachedBuffer sqlBuf;
5340 if (!SqlCachedBuffer.TryCreate(md, this, stateObj, out sqlBuf)) {
5341 return false;
5344 value.SqlCachedBuffer = sqlBuf;
5345 break;
5347 case TdsEnums.SQLDATE:
5348 case TdsEnums.SQLTIME:
5349 case TdsEnums.SQLDATETIME2:
5350 case TdsEnums.SQLDATETIMEOFFSET:
5351 if (!TryReadSqlDateTime(value, tdsType, length, md.scale, stateObj)) {
5352 return false;
5354 break;
5356 default:
5357 Debug.Assert(!isPlp, "ReadSqlValue calling ReadSqlValueInternal with plp data");
5358 if (!TryReadSqlValueInternal(value, tdsType, length, stateObj)) {
5359 return false;
5361 break;
5364 Debug.Assert((stateObj._longlen == 0) && (stateObj._longlenleft == 0), "ReadSqlValue did not read plp field completely, longlen =" + stateObj._longlen.ToString((IFormatProvider)null) + ",longlenleft=" + stateObj._longlenleft.ToString((IFormatProvider)null));
5365 return true;
5368 private bool TryReadSqlDateTime(SqlBuffer value, byte tdsType, int length, byte scale, TdsParserStateObject stateObj) {
5369 byte[] datetimeBuffer = new byte[length];
5371 if (!stateObj.TryReadByteArray(datetimeBuffer, 0, length)) {
5372 return false;
5375 switch (tdsType) {
5376 case TdsEnums.SQLDATE:
5377 Debug.Assert(length == 3, "invalid length for date type!");
5378 value.SetToDate(datetimeBuffer);
5379 break;
5381 case TdsEnums.SQLTIME:
5382 Debug.Assert(3 <= length && length <= 5, "invalid length for time type!");
5383 value.SetToTime(datetimeBuffer, length, scale, scale);
5384 break;
5386 case TdsEnums.SQLDATETIME2:
5387 Debug.Assert(6 <= length && length <= 8, "invalid length for datetime2 type!");
5388 value.SetToDateTime2(datetimeBuffer, length, scale, scale);
5389 break;
5391 case TdsEnums.SQLDATETIMEOFFSET:
5392 Debug.Assert(8 <= length && length <= 10, "invalid length for datetimeoffset type!");
5393 value.SetToDateTimeOffset(datetimeBuffer, length, scale, scale);
5394 break;
5396 default:
5397 Debug.Assert(false, "ReadSqlDateTime is called with the wrong tdsType");
5398 break;
5401 return true;
5404 internal bool TryReadSqlValueInternal(SqlBuffer value, byte tdsType, int length, TdsParserStateObject stateObj) {
5405 switch (tdsType) {
5406 case TdsEnums.SQLBIT:
5407 case TdsEnums.SQLBITN:
5408 Debug.Assert(length == 1, "invalid length for SqlBoolean type!");
5409 byte byteValue;
5410 if (!stateObj.TryReadByte(out byteValue)) {
5411 return false;
5413 value.Boolean = (byteValue != 0);
5414 break;
5416 case TdsEnums.SQLINTN:
5417 if (length == 1) {
5418 goto case TdsEnums.SQLINT1;
5420 else if (length == 2) {
5421 goto case TdsEnums.SQLINT2;
5423 else if (length == 4) {
5424 goto case TdsEnums.SQLINT4;
5426 else {
5427 goto case TdsEnums.SQLINT8;
5430 case TdsEnums.SQLINT1:
5431 Debug.Assert(length == 1, "invalid length for SqlByte type!");
5432 if (!stateObj.TryReadByte(out byteValue)) {
5433 return false;
5435 value.Byte = byteValue;
5436 break;
5438 case TdsEnums.SQLINT2:
5439 Debug.Assert(length == 2, "invalid length for SqlInt16 type!");
5440 short shortValue;
5441 if (!stateObj.TryReadInt16(out shortValue)) {
5442 return false;
5444 value.Int16 = shortValue;
5445 break;
5447 case TdsEnums.SQLINT4:
5448 Debug.Assert(length == 4, "invalid length for SqlInt32 type!");
5449 int intValue;
5450 if (!stateObj.TryReadInt32(out intValue)) {
5451 return false;
5453 value.Int32 = intValue;
5454 break;
5456 case TdsEnums.SQLINT8:
5457 Debug.Assert(length == 8, "invalid length for SqlInt64 type!");
5458 long longValue;
5459 if (!stateObj.TryReadInt64(out longValue)) {
5460 return false;
5462 value.Int64 = longValue;
5463 break;
5465 case TdsEnums.SQLFLTN:
5466 if (length == 4) {
5467 goto case TdsEnums.SQLFLT4;
5469 else {
5470 goto case TdsEnums.SQLFLT8;
5473 case TdsEnums.SQLFLT4:
5474 Debug.Assert(length == 4, "invalid length for SqlSingle type!");
5475 float singleValue;
5476 if (!stateObj.TryReadSingle(out singleValue)) {
5477 return false;
5479 value.Single = singleValue;
5480 break;
5482 case TdsEnums.SQLFLT8:
5483 Debug.Assert(length == 8, "invalid length for SqlDouble type!");
5484 double doubleValue;
5485 if (!stateObj.TryReadDouble(out doubleValue)) {
5486 return false;
5488 value.Double = doubleValue;
5489 break;
5491 case TdsEnums.SQLMONEYN:
5492 if (length == 4) {
5493 goto case TdsEnums.SQLMONEY4;
5495 else {
5496 goto case TdsEnums.SQLMONEY;
5499 case TdsEnums.SQLMONEY:
5501 int mid;
5502 uint lo;
5504 if (!stateObj.TryReadInt32(out mid)) {
5505 return false;
5507 if (!stateObj.TryReadUInt32(out lo)) {
5508 return false;
5511 long l = (((long)mid) << 0x20) + ((long)lo);
5513 value.SetToMoney(l);
5514 break;
5517 case TdsEnums.SQLMONEY4:
5518 if (!stateObj.TryReadInt32(out intValue)) {
5519 return false;
5521 value.SetToMoney(intValue);
5522 break;
5524 case TdsEnums.SQLDATETIMN:
5525 if (length == 4) {
5526 goto case TdsEnums.SQLDATETIM4;
5528 else {
5529 goto case TdsEnums.SQLDATETIME;
5532 case TdsEnums.SQLDATETIM4:
5533 ushort daypartShort, timepartShort;
5534 if (!stateObj.TryReadUInt16(out daypartShort)) {
5535 return false;
5537 if (!stateObj.TryReadUInt16(out timepartShort)) {
5538 return false;
5540 value.SetToDateTime(daypartShort, timepartShort * SqlDateTime.SQLTicksPerMinute);
5541 break;
5543 case TdsEnums.SQLDATETIME:
5544 int daypart;
5545 uint timepart;
5546 if (!stateObj.TryReadInt32(out daypart)) {
5547 return false;
5549 if (!stateObj.TryReadUInt32(out timepart)) {
5550 return false;
5552 value.SetToDateTime(daypart, (int)timepart);
5553 break;
5555 case TdsEnums.SQLUNIQUEID:
5557 Debug.Assert(length == 16, "invalid length for SqlGuid type!");
5559 byte[] b = new byte[length];
5561 if (!stateObj.TryReadByteArray(b, 0, length)) {
5562 return false;
5564 value.SqlGuid = new SqlGuid(b, true); // doesn't copy the byte array
5565 break;
5568 case TdsEnums.SQLBINARY:
5569 case TdsEnums.SQLBIGBINARY:
5570 case TdsEnums.SQLBIGVARBINARY:
5571 case TdsEnums.SQLVARBINARY:
5572 case TdsEnums.SQLIMAGE:
5574 // Note: Better not come here with plp data!!
5575 Debug.Assert(length <= TdsEnums.MAXSIZE);
5576 byte[] b = new byte[length];
5577 if (!stateObj.TryReadByteArray(b, 0, length)) {
5578 return false;
5580 value.SqlBinary = new SqlBinary(b, true); // doesn't copy the byte array
5582 break;
5585 case TdsEnums.SQLVARIANT:
5586 if (!TryReadSqlVariant(value, length, stateObj)) {
5587 return false;
5589 break;
5591 default:
5592 Debug.Assert(false, "Unknown SqlType!" + tdsType.ToString(CultureInfo.InvariantCulture));
5593 break;
5594 } // switch
5596 return true;
5600 // Read in a SQLVariant
5602 // SQLVariant looks like:
5603 // struct
5604 // {
5605 // BYTE TypeTag
5606 // BYTE cbPropBytes
5607 // BYTE[] Properties
5608 // BYTE[] DataVal
5609 // }
5610 internal bool TryReadSqlVariant(SqlBuffer value, int lenTotal, TdsParserStateObject stateObj) {
5611 Debug.Assert(_isShiloh == true, "Shouldn't be dealing with sql_variaint in pre-SQL2000 server!");
5612 // get the SQLVariant type
5613 byte type;
5614 if (!stateObj.TryReadByte(out type)) {
5615 return false;
5617 ushort lenMax = 0; // maximum lenData of value inside variant
5619 // read cbPropBytes
5620 byte cbPropsActual;
5621 if (!stateObj.TryReadByte(out cbPropsActual)) {
5622 return false;
5624 MetaType mt = MetaType.GetSqlDataType(type, 0 /*no user datatype*/, 0 /* no lenData, non-nullable type */);
5625 byte cbPropsExpected = mt.PropBytes;
5627 int lenConsumed = TdsEnums.SQLVARIANT_SIZE + cbPropsActual; // type, count of propBytes, and actual propBytes
5628 int lenData = lenTotal - lenConsumed; // length of actual data
5630 // read known properties and skip unknown properties
5631 Debug.Assert(cbPropsActual >= cbPropsExpected, "cbPropsActual is less that cbPropsExpected!");
5634 // now read the value
5636 switch (type) {
5637 case TdsEnums.SQLBIT:
5638 case TdsEnums.SQLINT1:
5639 case TdsEnums.SQLINT2:
5640 case TdsEnums.SQLINT4:
5641 case TdsEnums.SQLINT8:
5642 case TdsEnums.SQLFLT4:
5643 case TdsEnums.SQLFLT8:
5644 case TdsEnums.SQLMONEY:
5645 case TdsEnums.SQLMONEY4:
5646 case TdsEnums.SQLDATETIME:
5647 case TdsEnums.SQLDATETIM4:
5648 case TdsEnums.SQLUNIQUEID:
5649 if (!TryReadSqlValueInternal(value, type, lenData, stateObj)) {
5650 return false;
5652 break;
5654 case TdsEnums.SQLDECIMALN:
5655 case TdsEnums.SQLNUMERICN:
5657 Debug.Assert(cbPropsExpected == 2, "SqlVariant: invalid PropBytes for decimal/numeric type!");
5659 byte precision;
5660 if (!stateObj.TryReadByte(out precision)) {
5661 return false;
5663 byte scale;
5664 if (!stateObj.TryReadByte(out scale)) {
5665 return false;
5668 // skip over unknown properties
5669 if (cbPropsActual > cbPropsExpected) {
5670 if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) {
5671 return false;
5675 if (!TryReadSqlDecimal(value, TdsEnums.MAX_NUMERIC_LEN, precision, scale, stateObj)) {
5676 return false;
5678 break;
5681 case TdsEnums.SQLBIGBINARY:
5682 case TdsEnums.SQLBIGVARBINARY:
5683 //Debug.Assert(TdsEnums.VARNULL == lenData, "SqlVariant: data length for Binary indicates null?");
5684 Debug.Assert(cbPropsExpected == 2, "SqlVariant: invalid PropBytes for binary type!");
5686 if (!stateObj.TryReadUInt16(out lenMax)) {
5687 return false;
5689 Debug.Assert(lenMax != TdsEnums.SQL_USHORTVARMAXLEN, "bigvarbinary(max) in a sqlvariant");
5691 // skip over unknown properties
5692 if (cbPropsActual > cbPropsExpected) {
5693 if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) {
5694 return false;
5698 goto case TdsEnums.SQLBIT;
5700 case TdsEnums.SQLBIGCHAR:
5701 case TdsEnums.SQLBIGVARCHAR:
5702 case TdsEnums.SQLNCHAR:
5703 case TdsEnums.SQLNVARCHAR:
5705 Debug.Assert(cbPropsExpected == 7, "SqlVariant: invalid PropBytes for character type!");
5707 SqlCollation collation;
5708 if (!TryProcessCollation(stateObj, out collation)) {
5709 return false;
5712 if (!stateObj.TryReadUInt16(out lenMax)) {
5713 return false;
5715 Debug.Assert(lenMax != TdsEnums.SQL_USHORTVARMAXLEN, "bigvarchar(max) or nvarchar(max) in a sqlvariant");
5717 // skip over unknown properties
5718 if (cbPropsActual > cbPropsExpected) {
5719 if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) {
5720 return false;
5724 Encoding encoding = Encoding.GetEncoding(GetCodePage(collation, stateObj));
5725 if (!TryReadSqlStringValue(value, type, lenData, encoding, false, stateObj)) {
5726 return false;
5728 break;
5730 case TdsEnums.SQLDATE:
5731 if (!TryReadSqlDateTime(value, type, lenData, 0, stateObj)) {
5732 return false;
5734 break;
5736 case TdsEnums.SQLTIME:
5737 case TdsEnums.SQLDATETIME2:
5738 case TdsEnums.SQLDATETIMEOFFSET:
5740 Debug.Assert(cbPropsExpected == 1, "SqlVariant: invalid PropBytes for time/datetime2/datetimeoffset type!");
5742 byte scale;
5743 if (!stateObj.TryReadByte(out scale)) {
5744 return false;
5747 // skip over unknown properties
5748 if (cbPropsActual > cbPropsExpected) {
5749 if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) {
5750 return false;
5754 if (!TryReadSqlDateTime(value, type, lenData, scale, stateObj)) {
5755 return false;
5757 break;
5760 default:
5761 Debug.Assert(false, "Unknown tds type in SqlVariant!" + type.ToString(CultureInfo.InvariantCulture));
5762 break;
5763 } // switch
5765 return true;
5769 // Translates a com+ object -> SqlVariant
5770 // when the type is ambiguous, we always convert to the bigger type
5771 // note that we also write out the maxlen and actuallen members (4 bytes each)
5772 // in addition to the SQLVariant structure
5774 internal Task WriteSqlVariantValue(object value, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) {
5775 Debug.Assert(_isShiloh == true, "Shouldn't be dealing with sql_variant in pre-SQL2000 server!");
5777 // handle null values
5778 if (ADP.IsNull(value)) {
5779 WriteInt(TdsEnums.FIXEDNULL, stateObj); //maxlen
5780 WriteInt(TdsEnums.FIXEDNULL, stateObj); //actuallen
5781 return null;
5784 MetaType mt = MetaType.GetMetaTypeFromValue(value);
5786 // Special case data type correction for SqlMoney inside a SqlVariant.
5787 if ((TdsEnums.SQLNUMERICN == mt.TDSType) && (8 == length)) {
5788 // The caller will coerce all SqlTypes to native CLR types, which means SqlMoney will
5789 // coerce to decimal/SQLNUMERICN (via SqlMoney.Value call). In the case where the original
5790 // value was SqlMoney the caller will also pass in the metadata length for the SqlMoney type
5791 // which is 8 bytes. To honor the intent of the caller here we coerce this special case
5792 // input back to SqlMoney from decimal/SQLNUMERICN.
5793 mt = MetaType.GetMetaTypeFromValue(new SqlMoney((decimal)value));
5796 if (mt.IsAnsiType) {
5797 length = GetEncodingCharLength((string)value, length, 0, _defaultEncoding);
5800 // max and actual len are equal to
5801 // SQLVARIANTSIZE {type (1 byte) + cbPropBytes (1 byte)} + cbPropBytes + length (actual length of data in bytes)
5802 WriteInt(TdsEnums.SQLVARIANT_SIZE + mt.PropBytes + length, stateObj); // maxLen
5803 WriteInt(TdsEnums.SQLVARIANT_SIZE + mt.PropBytes + length, stateObj); // actualLen
5805 // write the SQLVariant header (type and cbPropBytes)
5806 stateObj.WriteByte(mt.TDSType);
5807 stateObj.WriteByte(mt.PropBytes);
5809 // now write the actual PropBytes and data
5810 switch (mt.TDSType) {
5811 case TdsEnums.SQLFLT4:
5812 WriteFloat((Single)value, stateObj);
5813 break;
5815 case TdsEnums.SQLFLT8:
5816 WriteDouble((Double)value, stateObj);
5817 break;
5819 case TdsEnums.SQLINT8:
5820 WriteLong((Int64)value, stateObj);
5821 break;
5823 case TdsEnums.SQLINT4:
5824 WriteInt((Int32)value, stateObj);
5825 break;
5827 case TdsEnums.SQLINT2:
5828 WriteShort((Int16)value, stateObj);
5829 break;
5831 case TdsEnums.SQLINT1:
5832 stateObj.WriteByte((byte)value);
5833 break;
5835 case TdsEnums.SQLBIT:
5836 if ((bool)value == true)
5837 stateObj.WriteByte(1);
5838 else
5839 stateObj.WriteByte(0);
5841 break;
5843 case TdsEnums.SQLBIGVARBINARY:
5845 byte[] b = (byte[])value;
5847 WriteShort(length, stateObj); // propbytes: varlen
5848 return stateObj.WriteByteArray(b, length, offset, canAccumulate);
5851 case TdsEnums.SQLBIGVARCHAR:
5853 string s = (string)value;
5855 WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info
5856 stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId
5857 WriteShort(length, stateObj); // propbyte: varlen
5858 return WriteEncodingChar(s, _defaultEncoding, stateObj, canAccumulate);
5861 case TdsEnums.SQLUNIQUEID:
5863 System.Guid guid = (System.Guid)value;
5864 byte[] b = guid.ToByteArray();
5866 Debug.Assert((length == b.Length) && (length == 16), "Invalid length for guid type in com+ object");
5867 stateObj.WriteByteArray(b, length, 0);
5868 break;
5871 case TdsEnums.SQLNVARCHAR:
5873 string s = (string)value;
5875 WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info
5876 stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId
5877 WriteShort(length, stateObj); // propbyte: varlen
5879 // string takes cchar, not cbyte so convert
5880 length >>= 1;
5881 return WriteString(s, length, offset, stateObj, canAccumulate);
5884 case TdsEnums.SQLDATETIME:
5886 TdsDateTime dt = MetaType.FromDateTime((DateTime)value, 8);
5888 WriteInt(dt.days, stateObj);
5889 WriteInt(dt.time, stateObj);
5890 break;
5893 case TdsEnums.SQLMONEY:
5895 WriteCurrency((Decimal)value, 8, stateObj);
5896 break;
5899 case TdsEnums.SQLNUMERICN:
5901 stateObj.WriteByte(mt.Precision); //propbytes: precision
5902 stateObj.WriteByte((byte)((Decimal.GetBits((Decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale
5903 WriteDecimal((Decimal)value, stateObj);
5904 break;
5907 case TdsEnums.SQLTIME:
5908 stateObj.WriteByte(mt.Scale); //propbytes: scale
5909 WriteTime((TimeSpan)value, mt.Scale, length, stateObj);
5910 break;
5912 case TdsEnums.SQLDATETIMEOFFSET:
5913 stateObj.WriteByte(mt.Scale); //propbytes: scale
5914 WriteDateTimeOffset((DateTimeOffset)value, mt.Scale, length, stateObj);
5915 break;
5917 default:
5918 Debug.Assert(false, "unknown tds type for sqlvariant!");
5919 break;
5920 } // switch
5921 // return point for accumulated writes, note: non-accumulated writes returned from their case statements
5922 return null;
5925 // todo: since we now know the difference between SqlWriteVariantValue and SqlWriteRowDataVariant we should consider
5926 // combining these tow methods.
5929 // Translates a com+ object -> SqlVariant
5930 // when the type is ambiguous, we always convert to the bigger type
5931 // note that we also write out the maxlen and actuallen members (4 bytes each)
5932 // in addition to the SQLVariant structure
5934 // Devnote: DataRows are preceeded by Metadata. The Metadata includes the MaxLen value.
5935 // Therefore the sql_variant value must not include the MaxLength. This is the major difference
5936 // between this method and WriteSqlVariantValue above.
5938 internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject stateObj, bool canAccumulate = true) {
5939 Debug.Assert(_isShiloh == true, "Shouldn't be dealing with sql_variant in pre-SQL2000 server!");
5941 // handle null values
5942 if ((null == value) || (DBNull.Value == value)) {
5943 WriteInt(TdsEnums.FIXEDNULL, stateObj);
5944 return null;
5947 MetaType metatype = MetaType.GetMetaTypeFromValue(value);
5948 int length = 0;
5950 if (metatype.IsAnsiType) {
5951 length = GetEncodingCharLength((string)value, length, 0, _defaultEncoding);
5954 switch (metatype.TDSType) {
5955 case TdsEnums.SQLFLT4:
5956 WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj);
5957 WriteFloat((Single)value, stateObj);
5958 break;
5960 case TdsEnums.SQLFLT8:
5961 WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj);
5962 WriteDouble((Double)value, stateObj);
5963 break;
5965 case TdsEnums.SQLINT8:
5966 WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj);
5967 WriteLong((Int64)value, stateObj);
5968 break;
5970 case TdsEnums.SQLINT4:
5971 WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj);
5972 WriteInt((Int32)value, stateObj);
5973 break;
5975 case TdsEnums.SQLINT2:
5976 WriteSqlVariantHeader(4, metatype.TDSType, metatype.PropBytes, stateObj);
5977 WriteShort((Int16)value, stateObj);
5978 break;
5980 case TdsEnums.SQLINT1:
5981 WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj);
5982 stateObj.WriteByte((byte)value);
5983 break;
5985 case TdsEnums.SQLBIT:
5986 WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj);
5987 if ((bool)value == true)
5988 stateObj.WriteByte(1);
5989 else
5990 stateObj.WriteByte(0);
5992 break;
5994 case TdsEnums.SQLBIGVARBINARY:
5996 byte[] b = (byte[])value;
5998 length = b.Length;
5999 WriteSqlVariantHeader(4 + length, metatype.TDSType, metatype.PropBytes, stateObj);
6000 WriteShort(length, stateObj); // propbytes: varlen
6001 return stateObj.WriteByteArray(b, length, 0, canAccumulate);
6004 case TdsEnums.SQLBIGVARCHAR:
6006 string s = (string)value;
6008 length = s.Length;
6009 WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj);
6010 WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info
6011 stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId
6012 WriteShort(length, stateObj);
6013 return WriteEncodingChar(s, _defaultEncoding, stateObj, canAccumulate);
6016 case TdsEnums.SQLUNIQUEID:
6018 System.Guid guid = (System.Guid)value;
6019 byte[] b = guid.ToByteArray();
6021 length = b.Length;
6022 Debug.Assert(length == 16, "Invalid length for guid type in com+ object");
6023 WriteSqlVariantHeader(18, metatype.TDSType, metatype.PropBytes, stateObj);
6024 stateObj.WriteByteArray(b, length, 0);
6025 break;
6028 case TdsEnums.SQLNVARCHAR:
6030 string s = (string)value;
6032 length = s.Length * 2;
6033 WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj);
6034 WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info
6035 stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId
6036 WriteShort(length, stateObj); // propbyte: varlen
6038 // string takes cchar, not cbyte so convert
6039 length >>= 1;
6040 return WriteString(s, length, 0, stateObj, canAccumulate);
6043 case TdsEnums.SQLDATETIME:
6045 TdsDateTime dt = MetaType.FromDateTime((DateTime)value, 8);
6047 WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj);
6048 WriteInt(dt.days, stateObj);
6049 WriteInt(dt.time, stateObj);
6050 break;
6053 case TdsEnums.SQLMONEY:
6055 WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj);
6056 WriteCurrency((Decimal)value, 8, stateObj);
6057 break;
6060 case TdsEnums.SQLNUMERICN:
6062 WriteSqlVariantHeader(21, metatype.TDSType, metatype.PropBytes, stateObj);
6063 stateObj.WriteByte(metatype.Precision); //propbytes: precision
6064 stateObj.WriteByte((byte)((Decimal.GetBits((Decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale
6065 WriteDecimal((Decimal)value, stateObj);
6066 break;
6069 case TdsEnums.SQLTIME:
6070 WriteSqlVariantHeader(8, metatype.TDSType, metatype.PropBytes, stateObj);
6071 stateObj.WriteByte(metatype.Scale); //propbytes: scale
6072 WriteTime((TimeSpan)value, metatype.Scale, 5, stateObj);
6073 break;
6075 case TdsEnums.SQLDATETIMEOFFSET:
6076 WriteSqlVariantHeader(13, metatype.TDSType, metatype.PropBytes, stateObj);
6077 stateObj.WriteByte(metatype.Scale); //propbytes: scale
6078 WriteDateTimeOffset((DateTimeOffset)value, metatype.Scale, 10, stateObj);
6079 break;
6081 default:
6082 Debug.Assert(false, "unknown tds type for sqlvariant!");
6083 break;
6084 } // switch
6085 // return point for accumualated writes, note: non-accumulated writes returned from their case statements
6086 return null;
6089 internal void WriteSqlVariantHeader(int length, byte tdstype, byte propbytes, TdsParserStateObject stateObj) {
6090 WriteInt(length, stateObj);
6091 stateObj.WriteByte(tdstype);
6092 stateObj.WriteByte(propbytes);
6095 internal void WriteSqlVariantDateTime2(DateTime value, TdsParserStateObject stateObj)
6097 MSS.SmiMetaData dateTime2MetaData = MSS.SmiMetaData.DefaultDateTime2;
6098 // NOTE: 3 bytes added here to support additional header information for variant - internal type, scale prop, scale
6099 WriteSqlVariantHeader((int)(dateTime2MetaData.MaxLength + 3), TdsEnums.SQLDATETIME2, 1 /* one scale prop */, stateObj);
6100 stateObj.WriteByte(dateTime2MetaData.Scale); //scale property
6101 WriteDateTime2(value, dateTime2MetaData.Scale, (int)(dateTime2MetaData.MaxLength), stateObj);
6104 internal void WriteSqlVariantDate(DateTime value, TdsParserStateObject stateObj)
6106 MSS.SmiMetaData dateMetaData = MSS.SmiMetaData.DefaultDate;
6107 // NOTE: 2 bytes added here to support additional header information for variant - internal type, scale prop (ignoring scale here)
6108 WriteSqlVariantHeader((int)(dateMetaData.MaxLength + 2), TdsEnums.SQLDATE, 0 /* one scale prop */, stateObj);
6109 WriteDate(value, stateObj);
6112 private byte[] SerializeSqlMoney(SqlMoney value, int length, TdsParserStateObject stateObj) {
6113 return SerializeCurrency(value.Value, length, stateObj);
6116 private void WriteSqlMoney(SqlMoney value, int length, TdsParserStateObject stateObj) {
6118 int[] bits = Decimal.GetBits(value.Value);
6120 // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by)
6121 bool isNeg = (0 != (bits[3] & unchecked((int)0x80000000)));
6122 long l = ((long)(uint)bits[1]) << 0x20 | (uint)bits[0];
6124 if (isNeg)
6125 l = -l;
6127 if (length == 4) {
6128 Decimal decimalValue = value.Value;
6130 // validate the value can be represented as a small money
6131 if (decimalValue < TdsEnums.SQL_SMALL_MONEY_MIN || decimalValue > TdsEnums.SQL_SMALL_MONEY_MAX) {
6132 throw SQL.MoneyOverflow(decimalValue.ToString(CultureInfo.InvariantCulture));
6135 WriteInt((int)l, stateObj);
6137 else {
6138 WriteInt((int)(l >> 0x20), stateObj);
6139 WriteInt((int)l, stateObj);
6143 private byte[] SerializeCurrency(Decimal value, int length, TdsParserStateObject stateObj) {
6144 SqlMoney m = new SqlMoney(value);
6145 int[] bits = Decimal.GetBits(m.Value);
6147 // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by)
6148 bool isNeg = (0 != (bits[3] & unchecked((int)0x80000000)));
6149 long l = ((long)(uint)bits[1]) << 0x20 | (uint)bits[0];
6151 if (isNeg)
6152 l = -l;
6154 if (length == 4) {
6155 // validate the value can be represented as a small money
6156 if (value < TdsEnums.SQL_SMALL_MONEY_MIN || value > TdsEnums.SQL_SMALL_MONEY_MAX) {
6157 throw SQL.MoneyOverflow(value.ToString(CultureInfo.InvariantCulture));
6160 // We normalize to allow conversion across data types. SMALLMONEY is serialized into a MONEY.
6161 length = 8;
6164 Debug.Assert (8 == length, "invalid length in SerializeCurrency");
6165 if (null == stateObj._bLongBytes) {
6166 stateObj._bLongBytes = new byte[8];
6169 byte[] bytes = stateObj._bLongBytes;
6170 int current = 0;
6172 byte[] bytesPart = SerializeInt((int)(l >> 0x20), stateObj);
6173 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6174 current += 4;
6176 bytesPart = SerializeInt((int)l, stateObj);
6177 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6179 return bytes;
6182 private void WriteCurrency(Decimal value, int length, TdsParserStateObject stateObj) {
6183 SqlMoney m = new SqlMoney(value);
6184 int[] bits = Decimal.GetBits(m.Value);
6186 // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by)
6187 bool isNeg = (0 != (bits[3] & unchecked((int)0x80000000)));
6188 long l = ((long)(uint)bits[1]) << 0x20 | (uint)bits[0];
6190 if (isNeg)
6191 l = -l;
6193 if (length == 4) {
6194 // validate the value can be represented as a small money
6195 if (value < TdsEnums.SQL_SMALL_MONEY_MIN || value > TdsEnums.SQL_SMALL_MONEY_MAX) {
6196 throw SQL.MoneyOverflow(value.ToString(CultureInfo.InvariantCulture));
6199 WriteInt((int)l, stateObj);
6201 else {
6202 WriteInt((int)(l >> 0x20), stateObj);
6203 WriteInt((int)l, stateObj);
6207 private byte[] SerializeDate(DateTime value) {
6208 long days = value.Subtract(DateTime.MinValue).Days;
6209 return SerializePartialLong(days, 3);
6212 private void WriteDate(DateTime value, TdsParserStateObject stateObj) {
6213 long days = value.Subtract(DateTime.MinValue).Days;
6214 WritePartialLong(days, 3, stateObj);
6217 private byte[] SerializeTime(TimeSpan value, byte scale, int length) {
6218 if (0 > value.Ticks || value.Ticks >= TimeSpan.TicksPerDay) {
6219 throw SQL.TimeOverflow(value.ToString());
6222 long time = value.Ticks / TdsEnums.TICKS_FROM_SCALE[scale];
6224 // We normalize to maximum precision to allow conversion across different precisions.
6225 time = time * TdsEnums.TICKS_FROM_SCALE[scale];
6226 length = TdsEnums.MAX_TIME_LENGTH;
6228 return SerializePartialLong(time, length);
6231 private void WriteTime(TimeSpan value, byte scale, int length, TdsParserStateObject stateObj) {
6232 if (0 > value.Ticks || value.Ticks >= TimeSpan.TicksPerDay) {
6233 throw SQL.TimeOverflow(value.ToString());
6235 long time = value.Ticks / TdsEnums.TICKS_FROM_SCALE[scale];
6236 WritePartialLong(time, length, stateObj);
6239 private byte[] SerializeDateTime2(DateTime value, byte scale, int length) {
6240 long time = value.TimeOfDay.Ticks / TdsEnums.TICKS_FROM_SCALE[scale]; // DateTime.TimeOfDay always returns a valid TimeSpan for Time
6242 // We normalize to maximum precision to allow conversion across different precisions.
6243 time = time * TdsEnums.TICKS_FROM_SCALE[scale];
6244 length = TdsEnums.MAX_DATETIME2_LENGTH;
6246 byte[] bytes = new byte[length];
6247 byte[] bytesPart;
6248 int current = 0;
6250 bytesPart = SerializePartialLong(time, length - 3);
6251 Buffer.BlockCopy(bytesPart, 0, bytes, current, length - 3);
6252 current += length - 3;
6254 bytesPart = SerializeDate(value);
6255 Buffer.BlockCopy(bytesPart, 0, bytes, current, 3);
6257 return bytes;
6260 private void WriteDateTime2(DateTime value, byte scale, int length, TdsParserStateObject stateObj) {
6261 long time = value.TimeOfDay.Ticks / TdsEnums.TICKS_FROM_SCALE[scale]; // DateTime.TimeOfDay always returns a valid TimeSpan for Time
6262 WritePartialLong(time, length - 3, stateObj);
6263 WriteDate(value, stateObj);
6266 private byte[] SerializeDateTimeOffset(DateTimeOffset value, byte scale, int length) {
6267 byte[] bytesPart;
6268 int current = 0;
6270 bytesPart = SerializeDateTime2(value.UtcDateTime, scale, length - 2);
6272 // We need to allocate the array after we have received the length of the serialized value
6273 // since it might be higher due to normalization.
6274 length = bytesPart.Length + 2;
6275 byte[] bytes = new byte[length];
6277 Buffer.BlockCopy(bytesPart, 0, bytes, current, length - 2);
6278 current += length - 2;
6280 Int16 offset = (Int16)value.Offset.TotalMinutes;
6281 bytes[current++] = (byte)(offset & 0xff);
6282 bytes[current++] = (byte)((offset >> 8) & 0xff);
6284 return bytes;
6287 private void WriteDateTimeOffset(DateTimeOffset value, byte scale, int length, TdsParserStateObject stateObj) {
6288 WriteDateTime2(value.UtcDateTime, scale, length - 2, stateObj);
6289 Int16 offset = (Int16)value.Offset.TotalMinutes;
6290 stateObj.WriteByte((byte)(offset & 0xff));
6291 stateObj.WriteByte((byte)((offset >> 8) & 0xff));
6294 private bool TryReadSqlDecimal(SqlBuffer value, int length, byte precision, byte scale, TdsParserStateObject stateObj) {
6295 byte byteValue;
6296 if (!stateObj.TryReadByte(out byteValue)) {
6297 return false;
6299 bool fPositive = (1 == byteValue);
6301 length = checked((int)length-1);
6303 int[] bits;
6304 if (!TryReadDecimalBits(length, stateObj, out bits)) {
6305 return false;
6308 value.SetToDecimal(precision, scale, fPositive, bits);
6309 return true;
6312 // @devnote: length should be size of decimal without the sign
6313 // @devnote: sign should have already been read off the wire
6314 private bool TryReadDecimalBits(int length, TdsParserStateObject stateObj, out int[] bits) {
6315 bits = stateObj._decimalBits; // used alloc'd array if we have one already
6316 int i;
6318 if (null == bits)
6319 bits = new int[4];
6320 else {
6321 for (i = 0; i < bits.Length; i++)
6322 bits[i] = 0;
6325 Debug.Assert((length > 0) &&
6326 (length <= TdsEnums.MAX_NUMERIC_LEN - 1) &&
6327 (length % 4 == 0), "decimal should have 4, 8, 12, or 16 bytes of data");
6329 int decLength = length >> 2;
6331 for (i = 0; i < decLength; i++) {
6332 // up to 16 bytes of data following the sign byte
6333 if (!stateObj.TryReadInt32(out bits[i])) {
6334 return false;
6338 return true;
6341 static internal SqlDecimal AdjustSqlDecimalScale(SqlDecimal d, int newScale) {
6342 if (d.Scale != newScale) {
6343 return SqlDecimal.AdjustScale(d, newScale - d.Scale, false /* Don't round, truncate. MDAC 69229 */);
6346 return d;
6349 static internal decimal AdjustDecimalScale(decimal value, int newScale) {
6350 int oldScale = (Decimal.GetBits(value)[3] & 0x00ff0000) >> 0x10;
6352 if (newScale != oldScale) {
6353 SqlDecimal num = new SqlDecimal(value);
6355 num = SqlDecimal.AdjustScale(num, newScale - oldScale, false /* Don't round, truncate. MDAC 69229 */);
6356 return num.Value;
6359 return value;
6362 internal byte[] SerializeSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) {
6363 if (null == stateObj._bDecimalBytes) {
6364 stateObj._bDecimalBytes = new byte[17];
6367 byte[] bytes = stateObj._bDecimalBytes;
6368 int current = 0;
6370 // sign
6371 if (d.IsPositive)
6372 bytes[current++] = 1;
6373 else
6374 bytes[current++] = 0;
6376 byte[] bytesPart = SerializeUnsignedInt(d.m_data1, stateObj);
6377 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6378 current += 4;
6379 bytesPart = SerializeUnsignedInt(d.m_data2, stateObj);
6380 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6381 current += 4;
6382 bytesPart = SerializeUnsignedInt(d.m_data3, stateObj);
6383 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6384 current += 4;
6385 bytesPart = SerializeUnsignedInt(d.m_data4, stateObj);
6386 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6388 return bytes;
6391 internal void WriteSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) {
6392 // sign
6393 if (d.IsPositive)
6394 stateObj.WriteByte(1);
6395 else
6396 stateObj.WriteByte(0);
6398 WriteUnsignedInt(d.m_data1, stateObj);
6399 WriteUnsignedInt(d.m_data2, stateObj);
6400 WriteUnsignedInt(d.m_data3, stateObj);
6401 WriteUnsignedInt(d.m_data4, stateObj);
6404 private byte[] SerializeDecimal(decimal value, TdsParserStateObject stateObj) {
6405 int[] decimalBits = Decimal.GetBits(value);
6406 if (null == stateObj._bDecimalBytes) {
6407 stateObj._bDecimalBytes = new byte[17];
6410 byte[] bytes = stateObj._bDecimalBytes;
6411 int current = 0;
6414 Returns a binary representation of a Decimal. The return value is an integer
6415 array with four elements. Elements 0, 1, and 2 contain the low, middle, and
6416 high 32 bits of the 96-bit integer part of the Decimal. Element 3 contains
6417 the scale factor and sign of the Decimal: bits 0-15 (the lower word) are
6418 unused; bits 16-23 contain a value between 0 and 28, indicating the power of
6419 10 to divide the 96-bit integer part by to produce the Decimal value; bits 24-
6420 30 are unused; and finally bit 31 indicates the sign of the Decimal value, 0
6421 meaning positive and 1 meaning negative.
6423 SQLDECIMAL/SQLNUMERIC has a byte stream of:
6424 struct {
6425 BYTE sign; // 1 if positive, 0 if negative
6426 BYTE data[];
6429 For TDS 7.0 and above, there are always 17 bytes of data
6432 // write the sign (note that COM and SQL are opposite)
6433 if (0x80000000 == (decimalBits[3] & 0x80000000))
6434 bytes[current++] = 0;
6435 else
6436 bytes[current++] = 1;
6438 byte[] bytesPart = SerializeInt(decimalBits[0], stateObj);
6439 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6440 current += 4;
6441 bytesPart = SerializeInt(decimalBits[1], stateObj);
6442 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6443 current += 4;
6444 bytesPart = SerializeInt(decimalBits[2], stateObj);
6445 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6446 current += 4;
6447 bytesPart = SerializeInt(0, stateObj);
6448 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6450 return bytes;
6453 private void WriteDecimal(decimal value, TdsParserStateObject stateObj) {
6454 stateObj._decimalBits = Decimal.GetBits(value);
6455 Debug.Assert(null != stateObj._decimalBits, "decimalBits should be filled in at TdsExecuteRPC time");
6458 Returns a binary representation of a Decimal. The return value is an integer
6459 array with four elements. Elements 0, 1, and 2 contain the low, middle, and
6460 high 32 bits of the 96-bit integer part of the Decimal. Element 3 contains
6461 the scale factor and sign of the Decimal: bits 0-15 (the lower word) are
6462 unused; bits 16-23 contain a value between 0 and 28, indicating the power of
6463 10 to divide the 96-bit integer part by to produce the Decimal value; bits 24-
6464 30 are unused; and finally bit 31 indicates the sign of the Decimal value, 0
6465 meaning positive and 1 meaning negative.
6467 SQLDECIMAL/SQLNUMERIC has a byte stream of:
6468 struct {
6469 BYTE sign; // 1 if positive, 0 if negative
6470 BYTE data[];
6473 For TDS 7.0 and above, there are always 17 bytes of data
6476 // write the sign (note that COM and SQL are opposite)
6477 if (0x80000000 == (stateObj._decimalBits[3] & 0x80000000))
6478 stateObj.WriteByte(0);
6479 else
6480 stateObj.WriteByte(1);
6482 WriteInt(stateObj._decimalBits[0], stateObj);
6483 WriteInt(stateObj._decimalBits[1], stateObj);
6484 WriteInt(stateObj._decimalBits[2], stateObj);
6485 WriteInt(0, stateObj);
6488 private void WriteIdentifier(string s, TdsParserStateObject stateObj) {
6489 if (null != s) {
6490 stateObj.WriteByte(checked((byte)s.Length));
6491 WriteString(s, stateObj);
6493 else {
6494 stateObj.WriteByte((byte)0);
6498 private void WriteIdentifierWithShortLength(string s, TdsParserStateObject stateObj) {
6499 if (null != s) {
6500 WriteShort(checked((short)s.Length), stateObj);
6501 WriteString(s, stateObj);
6503 else {
6504 WriteShort(0, stateObj);
6508 private Task WriteString(string s, TdsParserStateObject stateObj, bool canAccumulate = true) {
6509 return WriteString(s, s.Length, 0, stateObj, canAccumulate);
6512 internal byte[] SerializeCharArray(char[] carr, int length, int offset) {
6513 int cBytes = ADP.CharSize * length;
6514 byte[] bytes = new byte[cBytes];
6516 CopyCharsToBytes(carr, offset, bytes, 0, length);
6517 return bytes;
6520 internal Task WriteCharArray(char[] carr, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) {
6521 int cBytes = ADP.CharSize * length;
6523 // Perf shortcut: If it fits, write directly to the outBuff
6524 if(cBytes < (stateObj._outBuff.Length - stateObj._outBytesUsed)) {
6525 CopyCharsToBytes(carr, offset, stateObj._outBuff, stateObj._outBytesUsed, length);
6526 stateObj._outBytesUsed += cBytes;
6527 return null;
6529 else {
6530 if (stateObj._bTmp == null || stateObj._bTmp.Length < cBytes) {
6531 stateObj._bTmp = new byte[cBytes];
6534 CopyCharsToBytes(carr, offset, stateObj._bTmp, 0, length);
6535 return stateObj.WriteByteArray(stateObj._bTmp, cBytes, 0, canAccumulate);
6539 internal byte[] SerializeString(string s, int length, int offset) {
6540 int cBytes = ADP.CharSize * length;
6541 byte[] bytes = new byte[cBytes];
6543 CopyStringToBytes(s, offset, bytes, 0, length);
6544 return bytes;
6547 internal Task WriteString(string s, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) {
6548 int cBytes = ADP.CharSize * length;
6550 // Perf shortcut: If it fits, write directly to the outBuff
6551 if(cBytes < (stateObj._outBuff.Length - stateObj._outBytesUsed)) {
6552 CopyStringToBytes(s, offset, stateObj._outBuff, stateObj._outBytesUsed, length);
6553 stateObj._outBytesUsed += cBytes;
6554 return null;
6556 else {
6557 if (stateObj._bTmp == null || stateObj._bTmp.Length < cBytes) {
6558 stateObj._bTmp = new byte[cBytes];
6561 CopyStringToBytes(s, offset, stateObj._bTmp, 0, length);
6562 return stateObj.WriteByteArray(stateObj._bTmp, cBytes, 0, canAccumulate);
6567 private unsafe static void CopyCharsToBytes(char[] source, int sourceOffset, byte[] dest, int destOffset, int charLength) {
6568 // DEVNOTE: BE EXTREMELY CAREFULL in this method. Since it pins the buffers and copies the memory
6569 // directly, it bypasses all of the CLR's usual array-bounds checking. For maintainability, the checks
6570 // here should NOT be removed just because some other code will check them ahead of time.
6571 if (charLength < 0) {
6572 throw ADP.InvalidDataLength(charLength);
6575 if (checked(sourceOffset + charLength) > source.Length || sourceOffset < 0) {
6576 throw ADP.IndexOutOfRange(sourceOffset);
6579 // charLength >= 0 & checked conversion implies byteLength >= 0
6580 int byteLength = checked(charLength * ADP.CharSize);
6582 if (checked(destOffset + byteLength) > dest.Length || destOffset < 0) {
6583 throw ADP.IndexOutOfRange(destOffset);
6586 fixed (char* sourcePtr = source) {
6587 char* srcPtr = sourcePtr; // Can't increment the target of a Fixed statement
6588 srcPtr += sourceOffset; // char* increments by sizeof(char)
6589 fixed (byte* destinationPtr = dest) {
6590 byte* destPtr = destinationPtr;
6591 destPtr += destOffset;
6592 NativeOledbWrapper.MemoryCopy((IntPtr)destPtr, (IntPtr)srcPtr, byteLength);
6597 private unsafe static void CopyStringToBytes(string source, int sourceOffset, byte[] dest, int destOffset, int charLength) {
6598 // DEVNOTE: BE EXTREMELY CAREFULL in this method. Since it pins the buffers and copies the memory
6599 // directly, it bypasses all of the CLR's usual array-bounds checking. For maintainability, the checks
6600 // here should NOT be removed just because some other code will check them ahead of time.
6601 if (charLength < 0) {
6602 throw ADP.InvalidDataLength(charLength);
6605 if (checked(sourceOffset + charLength) > source.Length || sourceOffset < 0) {
6606 throw ADP.IndexOutOfRange(sourceOffset);
6609 // charLength >= 0 & checked conversion implies byteLength >= 0
6610 int byteLength = checked(charLength * ADP.CharSize);
6612 if (checked(destOffset + byteLength) > dest.Length || destOffset < 0) {
6613 throw ADP.IndexOutOfRange(destOffset);
6616 fixed (char* sourcePtr = source) {
6617 char* srcPtr = sourcePtr; // Can't increment the target of a Fixed statement
6618 srcPtr += sourceOffset; // char* increments by sizeof(char)
6619 fixed (byte* destinationPtr = dest) {
6620 byte* destPtr = destinationPtr;
6621 destPtr += destOffset;
6622 NativeOledbWrapper.MemoryCopy((IntPtr)destPtr, (IntPtr)srcPtr, byteLength);
6627 private Task WriteEncodingChar(string s, Encoding encoding, TdsParserStateObject stateObj, bool canAccumulate = true) {
6628 return WriteEncodingChar(s, s.Length, 0, encoding, stateObj, canAccumulate);
6631 private byte[] SerializeEncodingChar(string s, int numChars, int offset, Encoding encoding) {
6632 char[] charData;
6633 byte[] byteData = null;
6635 // if hitting 7.0 server, encoding will be null in metadata for columns or return values since
6636 // 7.0 has no support for multiple code pages in data - single code page support only
6637 if (encoding == null)
6638 encoding = _defaultEncoding;
6640 charData = s.ToCharArray(offset, numChars);
6642 byteData = new byte[encoding.GetByteCount(charData, 0, charData.Length)];
6643 encoding.GetBytes(charData, 0, charData.Length, byteData, 0);
6645 return byteData;
6648 private Task WriteEncodingChar(string s, int numChars, int offset, Encoding encoding, TdsParserStateObject stateObj, bool canAccumulate = true) {
6649 char[] charData;
6650 byte[] byteData;
6652 // if hitting 7.0 server, encoding will be null in metadata for columns or return values since
6653 // 7.0 has no support for multiple code pages in data - single code page support only
6654 if (encoding == null)
6655 encoding = _defaultEncoding;
6657 charData = s.ToCharArray(offset, numChars);
6659 // Optimization: if the entire string fits in the current buffer, then copy it directly
6660 int bytesLeft = stateObj._outBuff.Length - stateObj._outBytesUsed;
6661 if ((numChars <= bytesLeft) && (encoding.GetMaxByteCount(charData.Length) <= bytesLeft)) {
6662 int bytesWritten = encoding.GetBytes(charData, 0, charData.Length, stateObj._outBuff, stateObj._outBytesUsed);
6663 stateObj._outBytesUsed += bytesWritten;
6664 return null;
6666 else {
6667 byteData = encoding.GetBytes(charData, 0, numChars);
6668 Debug.Assert(byteData != null, "no data from encoding");
6669 return stateObj.WriteByteArray(byteData, byteData.Length, 0, canAccumulate);
6673 internal int GetEncodingCharLength(string value, int numChars, int charOffset, Encoding encoding) {
6676 if (value == null || value == ADP.StrEmpty) {
6677 return 0;
6680 // if hitting 7.0 server, encoding will be null in metadata for columns or return values since
6681 // 7.0 has no support for multiple code pages in data - single code page support only
6682 if (encoding == null) {
6683 if (null == _defaultEncoding) {
6684 ThrowUnsupportedCollationEncountered(null);
6687 encoding = _defaultEncoding;
6690 char[] charData = value.ToCharArray(charOffset, numChars);
6692 return encoding.GetByteCount(charData, 0, numChars);
6696 // Returns the data stream length of the data identified by tds type or SqlMetaData returns
6697 // Returns either the total size or the size of the first chunk for partially length prefixed types.
6699 internal bool TryGetDataLength(SqlMetaDataPriv colmeta, TdsParserStateObject stateObj, out ulong length) {
6700 // Handle Yukon specific tokens
6701 if (_isYukon && colmeta.metaType.IsPlp) {
6702 Debug.Assert(colmeta.tdsType == TdsEnums.SQLXMLTYPE ||
6703 colmeta.tdsType == TdsEnums.SQLBIGVARCHAR ||
6704 colmeta.tdsType == TdsEnums.SQLBIGVARBINARY ||
6705 colmeta.tdsType == TdsEnums.SQLNVARCHAR ||
6706 // Large UDTs is WinFS-only
6707 colmeta.tdsType == TdsEnums.SQLUDT,
6708 "GetDataLength:Invalid streaming datatype");
6709 return stateObj.TryReadPlpLength(true, out length);
6711 else {
6712 int intLength;
6713 if (!TryGetTokenLength(colmeta.tdsType, stateObj, out intLength)) {
6714 length = 0;
6715 return false;
6717 length = (ulong)intLength;
6718 return true;
6723 // returns the token length of the token or tds type
6724 // Returns -1 for partially length prefixed (plp) types for metadata info.
6725 // DOES NOT handle plp data streams correctly!!!
6726 // Plp data streams length information should be obtained from GetDataLength
6728 internal bool TryGetTokenLength(byte token, TdsParserStateObject stateObj, out int tokenLength) {
6729 Debug.Assert(token != 0, "0 length token!");
6731 switch (token) { // rules about SQLLenMask no longer apply to new tokens (as of 7.4)
6732 case TdsEnums.SQLFEATUREEXTACK:
6733 tokenLength = -1;
6734 return true;
6735 case TdsEnums.SQLSESSIONSTATE:
6736 case TdsEnums.SQLFEDAUTHINFO:
6737 return stateObj.TryReadInt32(out tokenLength);
6740 if (_isYukon) { // Handle Yukon specific exceptions
6741 if (token == TdsEnums.SQLUDT) { // special case for UDTs
6742 tokenLength = -1; // Should we return -1 or not call GetTokenLength for UDTs?
6743 return true;
6745 else if (token == TdsEnums.SQLRETURNVALUE) {
6746 tokenLength = -1; // In Yukon, the RETURNVALUE token stream no longer has length
6747 return true;
6749 else if (token == TdsEnums.SQLXMLTYPE) {
6750 ushort value;
6751 if (!stateObj.TryReadUInt16(out value)) {
6752 tokenLength = 0;
6753 return false;
6755 tokenLength = (int)value;
6756 Debug.Assert(tokenLength == TdsEnums.SQL_USHORTVARMAXLEN, "Invalid token stream for xml datatype");
6757 return true;
6761 switch (token & TdsEnums.SQLLenMask) {
6762 case TdsEnums.SQLFixedLen:
6763 tokenLength = ((0x01 << ((token & 0x0c) >> 2))) & 0xff;
6764 return true;
6765 case TdsEnums.SQLZeroLen:
6766 tokenLength = 0;
6767 return true;
6768 case TdsEnums.SQLVarLen:
6769 case TdsEnums.SQLVarCnt:
6770 if (0 != (token & 0x80)) {
6771 ushort value;
6772 if (!stateObj.TryReadUInt16(out value)) {
6773 tokenLength = 0;
6774 return false;
6776 tokenLength = value;
6777 return true;
6779 else if (0 == (token & 0x0c)) {
6781 if (!stateObj.TryReadInt32(out tokenLength)) {
6782 return false;
6784 return true;
6786 else {
6787 byte value;
6788 if (!stateObj.TryReadByte(out value)) {
6789 tokenLength = 0;
6790 return false;
6792 tokenLength = value;
6793 return true;
6795 default:
6796 Debug.Assert(false, "Unknown token length!");
6797 tokenLength = 0;
6798 return true;
6802 private void ProcessAttention(TdsParserStateObject stateObj) {
6803 if (_state == TdsParserState.Closed || _state == TdsParserState.Broken){
6804 return;
6806 Debug.Assert(stateObj._attentionSent, "invalid attempt to ProcessAttention, attentionSent == false!");
6808 // Attention processing scenarios:
6809 // 1) EOM packet with header ST_AACK bit plus DONE with status DONE_ATTN
6810 // 2) Packet without ST_AACK header bit but has DONE with status DONE_ATTN
6811 // 3) Secondary timeout occurs while reading, break connection
6813 // Since errors can occur and we need to cancel prior to throwing those errors, we
6814 // cache away error state and then process TDS for the attention. We restore those
6815 // errors after processing.
6816 stateObj.StoreErrorAndWarningForAttention();
6818 try {
6819 // Call run loop to process looking for attention ack.
6820 Run(RunBehavior.Attention, null, null, null, stateObj);
6822 catch (Exception e) {
6824 if (!ADP.IsCatchableExceptionType(e)) {
6825 throw;
6828 // If an exception occurs - break the connection.
6829 // Attention error will not be thrown in this case by Run(), but other failures may.
6830 ADP.TraceExceptionWithoutRethrow(e);
6831 _state = TdsParserState.Broken;
6832 _connHandler.BreakConnection();
6834 throw;
6837 stateObj.RestoreErrorAndWarningAfterAttention();
6839 Debug.Assert(!stateObj._attentionSent, "Invalid attentionSent state at end of ProcessAttention");
6842 static private int StateValueLength(int dataLen) {
6843 return dataLen < 0xFF ? (dataLen + 1) : (dataLen + 5);
6846 internal int WriteSessionRecoveryFeatureRequest(SessionData reconnectData, bool write /* if false just calculates the length */) {
6847 int len = 1;
6848 if (write) {
6849 _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_SRECOVERY);
6851 if (reconnectData == null) {
6852 if (write) {
6853 WriteInt(0, _physicalStateObj);
6855 len += 4;
6857 else {
6858 Debug.Assert(reconnectData._unrecoverableStatesCount == 0, "Unrecoverable state count should be 0");
6859 int initialLength = 0; // sizeof(DWORD) - length itself
6860 initialLength += 1 + 2 * TdsParserStaticMethods.NullAwareStringLength(reconnectData._initialDatabase);
6861 initialLength += 1 + 2 * TdsParserStaticMethods.NullAwareStringLength(reconnectData._initialLanguage);
6862 initialLength += (reconnectData._initialCollation == null) ? 1 : 6;
6863 for (int i = 0; i < SessionData._maxNumberOfSessionStates; i++) {
6864 if (reconnectData._initialState[i] != null) {
6865 initialLength += 1 /* StateId*/ + StateValueLength(reconnectData._initialState[i].Length);
6868 int currentLength = 0; // sizeof(DWORD) - length itself
6869 currentLength += 1 + 2 * (reconnectData._initialDatabase == reconnectData._database ? 0 : TdsParserStaticMethods.NullAwareStringLength(reconnectData._database));
6870 currentLength += 1 + 2 * (reconnectData._initialLanguage == reconnectData._language ? 0 : TdsParserStaticMethods.NullAwareStringLength(reconnectData._language));
6871 currentLength += (reconnectData._collation != null && !SqlCollation.AreSame(reconnectData._collation, reconnectData._initialCollation)) ? 6 : 1;
6872 bool[] writeState = new bool[SessionData._maxNumberOfSessionStates];
6873 for (int i = 0; i < SessionData._maxNumberOfSessionStates; i++) {
6874 if (reconnectData._delta[i] != null) {
6875 Debug.Assert(reconnectData._delta[i]._recoverable, "State should be recoverable");
6876 writeState[i] = true;
6877 if (reconnectData._initialState[i] != null && reconnectData._initialState[i].Length == reconnectData._delta[i]._dataLength) {
6878 writeState[i] = false;
6879 for (int j = 0; j < reconnectData._delta[i]._dataLength; j++) {
6880 if (reconnectData._initialState[i][j] != reconnectData._delta[i]._data[j]) {
6881 writeState[i] = true;
6882 break;
6886 if (writeState[i]) {
6887 currentLength += 1 /* StateId*/ + StateValueLength(reconnectData._delta[i]._dataLength);
6891 if (write) {
6892 WriteInt(8 + initialLength + currentLength, _physicalStateObj); // length of data w/o total length (initil+current+2*sizeof(DWORD))
6893 WriteInt(initialLength, _physicalStateObj);
6894 WriteIdentifier(reconnectData._initialDatabase, _physicalStateObj);
6895 WriteCollation(reconnectData._initialCollation, _physicalStateObj);
6896 WriteIdentifier(reconnectData._initialLanguage, _physicalStateObj);
6897 for (int i = 0; i < SessionData._maxNumberOfSessionStates; i++) {
6898 if (reconnectData._initialState[i] != null) {
6899 _physicalStateObj.WriteByte((byte)i);
6900 if (reconnectData._initialState[i].Length < 0xFF) {
6901 _physicalStateObj.WriteByte((byte)reconnectData._initialState[i].Length);
6903 else {
6904 _physicalStateObj.WriteByte(0xFF);
6905 WriteInt(reconnectData._initialState[i].Length, _physicalStateObj);
6907 _physicalStateObj.WriteByteArray(reconnectData._initialState[i], reconnectData._initialState[i].Length, 0);
6910 WriteInt(currentLength, _physicalStateObj);
6911 WriteIdentifier(reconnectData._database != reconnectData._initialDatabase ? reconnectData._database : null, _physicalStateObj);
6912 WriteCollation(SqlCollation.AreSame(reconnectData._initialCollation, reconnectData._collation) ? null : reconnectData._collation, _physicalStateObj);
6913 WriteIdentifier(reconnectData._language != reconnectData._initialLanguage ? reconnectData._language : null, _physicalStateObj);
6914 for (int i = 0; i < SessionData._maxNumberOfSessionStates; i++) {
6915 if (writeState[i]) {
6916 _physicalStateObj.WriteByte((byte)i);
6917 if (reconnectData._delta[i]._dataLength < 0xFF) {
6918 _physicalStateObj.WriteByte((byte)reconnectData._delta[i]._dataLength);
6920 else {
6921 _physicalStateObj.WriteByte(0xFF);
6922 WriteInt(reconnectData._delta[i]._dataLength, _physicalStateObj);
6924 _physicalStateObj.WriteByteArray(reconnectData._delta[i]._data, reconnectData._delta[i]._dataLength, 0);
6928 len += initialLength + currentLength + 12 /* length fields (initial, current, total) */;
6930 return len;
6933 internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionData fedAuthFeatureData,
6934 bool write /* if false just calculates the length */) {
6935 Debug.Assert(fedAuthFeatureData.libraryType == TdsEnums.FedAuthLibrary.ADAL || fedAuthFeatureData.libraryType == TdsEnums.FedAuthLibrary.SecurityToken,
6936 "only fed auth library type ADAL and Security Token are supported in writing feature request");
6938 int dataLen = 0;
6939 int totalLen = 0;
6941 // set dataLen and totalLen
6942 switch (fedAuthFeatureData.libraryType) {
6943 case TdsEnums.FedAuthLibrary.ADAL:
6944 dataLen = 2; // length of feature data = 1 byte for library and echo + 1 byte for workflow
6945 break;
6946 case TdsEnums.FedAuthLibrary.SecurityToken:
6947 Debug.Assert(fedAuthFeatureData.accessToken != null, "AccessToken should not be null.");
6948 dataLen = 1 + sizeof(int) + fedAuthFeatureData.accessToken.Length; // length of feature data = 1 byte for library and echo, security token length and sizeof(int) for token lengh itself
6949 break;
6950 default:
6951 Debug.Assert(false, "Unrecognized library type for fedauth feature extension request");
6952 break;
6955 totalLen = dataLen + 5; // length of feature id (1 byte), data length field (4 bytes), and feature data (dataLen)
6957 // write feature id
6958 if (write) {
6959 _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_FEDAUTH);
6961 // set options
6962 byte options = 0x00;
6964 // set upper 7 bits of options to indicate fed auth library type
6965 switch (fedAuthFeatureData.libraryType) {
6966 case TdsEnums.FedAuthLibrary.ADAL:
6967 Debug.Assert(_connHandler._federatedAuthenticationInfoRequested == true, "_federatedAuthenticationInfoRequested field should be true");
6968 options |= TdsEnums.FEDAUTHLIB_ADAL << 1;
6969 break;
6970 case TdsEnums.FedAuthLibrary.SecurityToken:
6971 Debug.Assert(_connHandler._federatedAuthenticationRequested == true, "_federatedAuthenticationRequested field should be true");
6972 options |= TdsEnums.FEDAUTHLIB_SECURITYTOKEN << 1;
6973 break;
6974 default:
6975 Debug.Assert(false, "Unrecognized FedAuthLibrary type for feature extension request");
6976 break;
6979 options |= (byte)(fedAuthFeatureData.fedAuthRequiredPreLoginResponse == true ? 0x01 : 0x00);
6981 // write dataLen and options
6982 WriteInt(dataLen, _physicalStateObj);
6983 _physicalStateObj.WriteByte(options);
6985 // write workflow for FedAuthLibrary.ADAL
6986 // write accessToken for FedAuthLibrary.SecurityToken
6987 switch (fedAuthFeatureData.libraryType) {
6988 case TdsEnums.FedAuthLibrary.ADAL:
6989 byte workflow = 0x00;
6990 switch (fedAuthFeatureData.authentication) {
6991 case SqlAuthenticationMethod.ActiveDirectoryPassword:
6992 workflow = TdsEnums.ADALWORKFLOW_ACTIVEDIRECTORYPASSWORD;
6993 break;
6994 case SqlAuthenticationMethod.ActiveDirectoryIntegrated:
6995 workflow = TdsEnums.ADALWORKFLOW_ACTIVEDIRECTORYINTEGRATED;
6996 break;
6997 default:
6998 Debug.Assert(false, "Unrecognized Authentication type for fedauth ADAL request");
6999 break;
7002 _physicalStateObj.WriteByte(workflow);
7003 break;
7004 case TdsEnums.FedAuthLibrary.SecurityToken:
7005 WriteInt(fedAuthFeatureData.accessToken.Length, _physicalStateObj);
7006 _physicalStateObj.WriteByteArray(fedAuthFeatureData.accessToken, fedAuthFeatureData.accessToken.Length, 0);
7007 break;
7008 default:
7009 Debug.Assert(false, "Unrecognized FedAuthLibrary type for feature extension request");
7010 break;
7013 return totalLen;
7016 internal int WriteTceFeatureRequest (bool write /* if false just calculates the length */) {
7017 int len = 6; // (1byte = featureID, 4bytes = featureData length, 1 bytes = Version
7019 if (write) {
7020 // Write Feature ID, legth of the version# field and TCE Version#
7021 _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_TCE);
7022 WriteInt (1, _physicalStateObj);
7023 _physicalStateObj.WriteByte(TdsEnums.MAX_SUPPORTED_TCE_VERSION);
7026 return len; // size of data written
7029 internal int WriteGlobalTransactionsFeatureRequest(bool write /* if false just calculates the length */) {
7030 int len = 5; // 1byte = featureID, 4bytes = featureData length
7032 if (write) {
7033 // Write Feature ID
7034 _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS);
7035 WriteInt(0, _physicalStateObj); // we don't send any data
7038 return len;
7041 internal void TdsLogin(SqlLogin rec,
7042 TdsEnums.FeatureExtension requestedFeatures,
7043 SessionData recoverySessionData,
7044 FederatedAuthenticationFeatureExtensionData? fedAuthFeatureExtensionData) {
7045 _physicalStateObj.SetTimeoutSeconds(rec.timeout);
7047 Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request");
7048 Debug.Assert(TdsEnums.MAXLEN_HOSTNAME>=rec.hostName.Length, "_workstationId.Length exceeds the max length for this value");
7050 Debug.Assert(!(rec.useSSPI && _connHandler._fedAuthRequired), "Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option.");
7051 Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth");
7052 Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request");
7053 Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData.");
7055 Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_USERNAME >= rec.userName.Length), "_userID.Length exceeds the max length for this value");
7056 Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_USERNAME >= rec.credential.UserId.Length), "_credential.UserId.Length exceeds the max length for this value");
7058 Debug.Assert(rec.password == null || (rec.password != null && TdsEnums.MAXLEN_PASSWORD>=rec.password.Length), "_password.Length exceeds the max length for this value");
7059 Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_PASSWORD >= rec.credential.Password.Length), "_credential.Password.Length exceeds the max length for this value");
7061 Debug.Assert(rec.credential != null || rec.userName != null || rec.password != null, "cannot mix the new secure password system and the connection string based password");
7062 Debug.Assert(rec.newSecurePassword != null || rec.newPassword != null, "cannot have both new secure change password and string based change password");
7063 Debug.Assert(TdsEnums.MAXLEN_APPNAME>=rec.applicationName.Length, "_applicationName.Length exceeds the max length for this value");
7064 Debug.Assert(TdsEnums.MAXLEN_SERVERNAME>=rec.serverName.Length, "_dataSource.Length exceeds the max length for this value");
7065 Debug.Assert(TdsEnums.MAXLEN_LANGUAGE>=rec.language.Length, "_currentLanguage .Length exceeds the max length for this value");
7066 Debug.Assert(TdsEnums.MAXLEN_DATABASE>=rec.database.Length, "_initialCatalog.Length exceeds the max length for this value");
7067 Debug.Assert(TdsEnums.MAXLEN_ATTACHDBFILE>=rec.attachDBFilename.Length, "_attachDBFileName.Length exceeds the max length for this value");
7069 Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point.");
7070 _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
7071 _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth);
7073 // get the password up front to use in sspi logic below
7074 byte[] encryptedPassword = null;
7075 byte[] encryptedChangePassword = null;
7076 int encryptedPasswordLengthInBytes;
7077 int encryptedChangePasswordLengthInBytes;
7078 bool useFeatureExt = (requestedFeatures != TdsEnums.FeatureExtension.None);
7080 string userName;
7082 if (rec.credential != null) {
7083 userName = rec.credential.UserId;
7084 encryptedPasswordLengthInBytes = rec.credential.Password.Length * 2;
7086 else {
7087 userName = rec.userName;
7088 encryptedPassword = TdsParserStaticMethods.EncryptPassword(rec.password);
7089 encryptedPasswordLengthInBytes = encryptedPassword.Length; // password in clear text is already encrypted and its length is in byte
7092 if (rec.newSecurePassword != null) {
7093 encryptedChangePasswordLengthInBytes = rec.newSecurePassword.Length * 2;
7095 else {
7096 encryptedChangePassword = TdsParserStaticMethods.EncryptPassword(rec.newPassword);
7097 encryptedChangePasswordLengthInBytes = encryptedChangePassword.Length;
7101 // set the message type
7102 _physicalStateObj._outputMessageType = TdsEnums.MT_LOGIN7;
7104 // length in bytes
7105 int length = TdsEnums.YUKON_LOG_REC_FIXED_LEN;
7107 string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME;
7108 Debug.Assert(TdsEnums.MAXLEN_CLIENTINTERFACE >= clientInterfaceName.Length, "cchCltIntName can specify at most 128 unicode characters. See Tds spec");
7110 // add up variable-len portions (multiply by 2 for byte len of char strings)
7112 checked {
7113 length += (rec.hostName.Length + rec.applicationName.Length +
7114 rec.serverName.Length + clientInterfaceName.Length +
7115 rec.language.Length + rec.database.Length +
7116 rec.attachDBFilename.Length) * 2;
7117 if (useFeatureExt) {
7118 length += 4;
7122 // allocate memory for SSPI variables
7123 byte[] outSSPIBuff = null;
7124 UInt32 outSSPILength = 0;
7126 // only add lengths of password and username if not using SSPI or requesting federated authentication info
7127 if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) {
7128 checked {
7129 length += (userName.Length * 2) + encryptedPasswordLengthInBytes
7130 + encryptedChangePasswordLengthInBytes;
7133 else {
7134 if (rec.useSSPI) {
7135 // now allocate proper length of buffer, and set length
7136 outSSPIBuff = new byte[s_maxSSPILength];
7137 outSSPILength = s_maxSSPILength;
7139 // Call helper function for SSPI data and actual length.
7140 // Since we don't have SSPI data from the server, send null for the
7141 // byte[] buffer and 0 for the int length.
7142 Debug.Assert(SniContext.Snix_Login==_physicalStateObj.SniContext, String.Format((IFormatProvider)null, "Unexpected SniContext. Expecting Snix_Login, actual value is '{0}'", _physicalStateObj.SniContext));
7143 _physicalStateObj.SniContext = SniContext.Snix_LoginSspi;
7144 SSPIData(null, 0, outSSPIBuff, ref outSSPILength);
7145 if (outSSPILength > Int32.MaxValue) {
7146 throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503
7148 _physicalStateObj.SniContext=SniContext.Snix_Login;
7150 checked {
7151 length += (Int32)outSSPILength;
7156 int feOffset = length;
7158 if (useFeatureExt) {
7159 checked {
7160 if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) {
7161 length += WriteSessionRecoveryFeatureRequest(recoverySessionData, false);
7163 if ((requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0) {
7164 Debug.Assert(fedAuthFeatureExtensionData != null, "fedAuthFeatureExtensionData should not null.");
7165 length += WriteFedAuthFeatureRequest(fedAuthFeatureExtensionData.Value, write:false);
7167 if ((requestedFeatures & TdsEnums.FeatureExtension.Tce) != 0) {
7168 length += WriteTceFeatureRequest (false);
7170 if ((requestedFeatures & TdsEnums.FeatureExtension.GlobalTransactions) != 0) {
7171 length += WriteGlobalTransactionsFeatureRequest(false);
7173 length++; // for terminator
7177 try {
7178 WriteInt(length, _physicalStateObj);
7179 if (recoverySessionData == null) {
7180 WriteInt((TdsEnums.DENALI_MAJOR << 24) | (TdsEnums.DENALI_INCREMENT << 16) | TdsEnums.DENALI_MINOR, _physicalStateObj);
7182 else {
7183 WriteUnsignedInt(recoverySessionData._tdsVersion, _physicalStateObj);
7185 WriteInt(rec.packetSize, _physicalStateObj);
7186 WriteInt(TdsEnums.CLIENT_PROG_VER, _physicalStateObj);
7187 WriteInt(TdsParserStaticMethods.GetCurrentProcessIdForTdsLoginOnly(), _physicalStateObj); //MDAC 84718
7188 WriteInt(0, _physicalStateObj); // connectionID is unused
7190 // Log7Flags (DWORD)
7191 int log7Flags = 0;
7194 Current snapshot from TDS spec with the offsets added:
7195 0) fByteOrder:1, // byte order of numeric data types on client
7196 1) fCharSet:1, // character set on client
7197 2) fFloat:2, // Type of floating point on client
7198 4) fDumpLoad:1, // Dump/Load and BCP enable
7199 5) fUseDb:1, // USE notification
7200 6) fDatabase:1, // Initial database fatal flag
7201 7) fSetLang:1, // SET LANGUAGE notification
7202 8) fLanguage:1, // Initial language fatal flag
7203 9) fODBC:1, // Set if client is ODBC driver
7204 10) fTranBoundary:1, // Transaction boundary notification
7205 11) fDelegatedSec:1, // Security with delegation is available
7206 12) fUserType:3, // Type of user
7207 15) fIntegratedSecurity:1, // Set if client is using integrated security
7208 16) fSQLType:4, // Type of SQL sent from client
7209 20) fOLEDB:1, // Set if client is OLEDB driver
7210 21) fSpare1:3, // first bit used for read-only intent, rest unused
7211 24) fResetPassword:1, // set if client wants to reset password
7212 25) fNoNBCAndSparse:1, // set if client does not support NBC and Sparse column
7213 26) fUserInstance:1, // This connection wants to connect to a SQL "user instance"
7214 27) fUnknownCollationHandling:1, // This connection can handle unknown collation correctly.
7215 28) fExtension:1 // Extensions are used
7216 32 - total
7219 // first byte
7220 log7Flags |= TdsEnums.USE_DB_ON << 5;
7221 log7Flags |= TdsEnums.INIT_DB_FATAL << 6;
7222 log7Flags |= TdsEnums.SET_LANG_ON << 7;
7224 // second byte
7225 log7Flags |= TdsEnums.INIT_LANG_FATAL << 8;
7226 log7Flags |= TdsEnums.ODBC_ON << 9;
7227 if (rec.useReplication) {
7228 log7Flags |= TdsEnums.REPL_ON << 12;
7230 if (rec.useSSPI) {
7231 log7Flags |= TdsEnums.SSPI_ON << 15;
7234 // third byte
7235 if (rec.readOnlyIntent) {
7236 log7Flags |= TdsEnums.READONLY_INTENT_ON << 21; // read-only intent flag is a first bit of fSpare1
7239 // 4th one
7240 if (!ADP.IsEmpty(rec.newPassword) || (rec.newSecurePassword != null && rec.newSecurePassword.Length != 0)) {
7241 log7Flags |= 1 << 24;
7243 if (rec.userInstance) {
7244 log7Flags |= 1 << 26;
7247 if (useFeatureExt) {
7248 log7Flags |= 1 << 28;
7251 WriteInt(log7Flags, _physicalStateObj);
7252 if (Bid.AdvancedOn) {
7253 Bid.Trace("<sc.TdsParser.TdsLogin|ADV> %d#, TDS Login7 flags = %d:\n", ObjectID, log7Flags);
7256 WriteInt(0, _physicalStateObj); // ClientTimeZone is not used
7257 WriteInt(0, _physicalStateObj); // LCID is unused by server
7259 // Start writing offset and length of variable length portions
7260 int offset = TdsEnums.YUKON_LOG_REC_FIXED_LEN;
7262 // write offset/length pairs
7264 // note that you must always set ibHostName since it indicaters the beginning of the variable length section of the login record
7265 WriteShort(offset, _physicalStateObj); // host name offset
7266 WriteShort(rec.hostName.Length, _physicalStateObj);
7267 offset += rec.hostName.Length * 2;
7269 // Only send user/password over if not fSSPI or fed auth ADAL... If both user/password and SSPI are in login
7270 // rec, only SSPI is used. Confirmed same bahavior as in luxor.
7271 if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) {
7272 WriteShort(offset, _physicalStateObj); // userName offset
7273 WriteShort(userName.Length, _physicalStateObj);
7274 offset += userName.Length * 2;
7276 // the encrypted password is a byte array - so length computations different than strings
7277 WriteShort(offset, _physicalStateObj); // password offset
7278 WriteShort(encryptedPasswordLengthInBytes / 2, _physicalStateObj);
7279 offset += encryptedPasswordLengthInBytes;
7281 else {
7282 // case where user/password data is not used, send over zeros
7283 WriteShort(0, _physicalStateObj); // userName offset
7284 WriteShort(0, _physicalStateObj);
7285 WriteShort(0, _physicalStateObj); // password offset
7286 WriteShort(0, _physicalStateObj);
7289 WriteShort(offset, _physicalStateObj); // app name offset
7290 WriteShort(rec.applicationName.Length, _physicalStateObj);
7291 offset += rec.applicationName.Length * 2;
7293 WriteShort(offset, _physicalStateObj); // server name offset
7294 WriteShort(rec.serverName.Length, _physicalStateObj);
7295 offset += rec.serverName.Length * 2;
7297 WriteShort(offset, _physicalStateObj);
7298 if (useFeatureExt) {
7299 WriteShort(4, _physicalStateObj); // length of ibFeatgureExtLong (which is a DWORD)
7300 offset += 4;
7302 else {
7303 WriteShort(0, _physicalStateObj); // unused (was remote password ?)
7306 WriteShort(offset, _physicalStateObj); // client interface name offset
7307 WriteShort(clientInterfaceName.Length, _physicalStateObj);
7308 offset += clientInterfaceName.Length * 2;
7310 WriteShort(offset, _physicalStateObj); // language name offset
7311 WriteShort(rec.language.Length, _physicalStateObj);
7312 offset += rec.language.Length * 2;
7314 WriteShort(offset, _physicalStateObj); // database name offset
7315 WriteShort(rec.database.Length, _physicalStateObj);
7316 offset += rec.database.Length * 2;
7320 if (null == s_nicAddress)
7321 s_nicAddress = TdsParserStaticMethods.GetNetworkPhysicalAddressForTdsLoginOnly();
7323 _physicalStateObj.WriteByteArray(s_nicAddress, s_nicAddress.Length, 0);
7325 WriteShort(offset, _physicalStateObj); // ibSSPI offset
7326 if (rec.useSSPI) {
7327 WriteShort((int)outSSPILength, _physicalStateObj);
7328 offset += (int)outSSPILength;
7330 else {
7331 WriteShort(0, _physicalStateObj);
7334 WriteShort(offset, _physicalStateObj); // DB filename offset
7335 WriteShort(rec.attachDBFilename.Length, _physicalStateObj);
7336 offset += rec.attachDBFilename.Length * 2;
7338 WriteShort(offset, _physicalStateObj); // reset password offset
7339 WriteShort(encryptedChangePasswordLengthInBytes / 2, _physicalStateObj);
7341 WriteInt(0, _physicalStateObj); // reserved for chSSPI
7343 // write variable length portion
7344 WriteString(rec.hostName, _physicalStateObj);
7346 // if we are using SSPI or fed auth ADAL, do not send over username/password, since we will use SSPI instead
7347 // same behavior as Luxor
7348 if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) {
7349 WriteString(userName, _physicalStateObj);
7351 // Cache offset in packet for tracing.
7352 _physicalStateObj._tracePasswordOffset = _physicalStateObj._outBytesUsed;
7353 _physicalStateObj._tracePasswordLength = encryptedPasswordLengthInBytes;
7355 if (rec.credential != null) {
7356 _physicalStateObj.WriteSecureString(rec.credential.Password);
7358 else {
7359 _physicalStateObj.WriteByteArray(encryptedPassword, encryptedPasswordLengthInBytes, 0);
7363 WriteString(rec.applicationName, _physicalStateObj);
7364 WriteString(rec.serverName, _physicalStateObj);
7366 // write ibFeatureExtLong
7367 if (useFeatureExt) {
7368 WriteInt(feOffset, _physicalStateObj);
7371 WriteString(clientInterfaceName, _physicalStateObj);
7372 WriteString(rec.language, _physicalStateObj);
7373 WriteString(rec.database, _physicalStateObj);
7375 // send over SSPI data if we are using SSPI
7376 if (rec.useSSPI)
7377 _physicalStateObj.WriteByteArray(outSSPIBuff, (int)outSSPILength, 0);
7379 WriteString(rec.attachDBFilename, _physicalStateObj);
7380 if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) {
7381 // Cache offset in packet for tracing.
7382 _physicalStateObj._traceChangePasswordOffset = _physicalStateObj._outBytesUsed;
7383 _physicalStateObj._traceChangePasswordLength = encryptedChangePasswordLengthInBytes;
7384 if (rec.newSecurePassword != null) {
7385 _physicalStateObj.WriteSecureString(rec.newSecurePassword);
7387 else {
7388 _physicalStateObj.WriteByteArray(encryptedChangePassword, encryptedChangePasswordLengthInBytes, 0);
7392 if (useFeatureExt) {
7393 if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) {
7394 WriteSessionRecoveryFeatureRequest(recoverySessionData, true);
7396 if ((requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0) {
7397 Bid.Trace("<sc.TdsParser.TdsLogin|SEC> Sending federated authentication feature request\n");
7398 Debug.Assert(fedAuthFeatureExtensionData != null, "fedAuthFeatureExtensionData should not null.");
7399 WriteFedAuthFeatureRequest(fedAuthFeatureExtensionData.Value, write: true);
7401 if ((requestedFeatures & TdsEnums.FeatureExtension.Tce) != 0) {
7402 WriteTceFeatureRequest (true);
7404 if ((requestedFeatures & TdsEnums.FeatureExtension.GlobalTransactions) != 0) {
7405 WriteGlobalTransactionsFeatureRequest(true);
7407 _physicalStateObj.WriteByte(0xFF); // terminator
7409 } // try
7410 catch (Exception e) {
7412 if (ADP.IsCatchableExceptionType(e)) {
7413 // be sure to wipe out our buffer if we started sending stuff
7414 _physicalStateObj._outputPacketNumber = 1; // end of message - reset to 1 - per ramas
7415 _physicalStateObj.ResetBuffer();
7418 throw;
7421 _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
7422 _physicalStateObj.ResetSecurePasswordsInfomation(); // Password information is needed only from Login process; done with writing login packet and should clear information
7423 _physicalStateObj._pendingData = true;
7424 _physicalStateObj._messageStatus = 0;
7425 }// tdsLogin
7427 /// <summary>
7428 /// Send the access token to the server.
7429 /// </summary>
7430 /// <param name="fedAuthToken">Type encapuslating a Federated Authentication access token.</param>
7431 internal void SendFedAuthToken(SqlFedAuthToken fedAuthToken) {
7432 Debug.Assert(fedAuthToken != null, "fedAuthToken cannot be null");
7433 Debug.Assert(fedAuthToken.accessToken != null, "fedAuthToken.accessToken cannot be null");
7436 Bid.Trace("<sc.TdsParser.SendFedAuthToken|SEC> Sending federated authentication token\n");
7438 _physicalStateObj._outputMessageType = TdsEnums.MT_FEDAUTH;
7440 byte[] accessToken = fedAuthToken.accessToken;
7442 // Send total length (length of token plus 4 bytes for the token length field)
7443 // If we were sending a nonce, this would include that length as well
7444 WriteUnsignedInt((uint)accessToken.Length + sizeof(uint), _physicalStateObj);
7446 // Send length of token
7447 WriteUnsignedInt((uint)accessToken.Length, _physicalStateObj);
7449 // Send federated authentication access token
7450 _physicalStateObj.WriteByteArray(accessToken, accessToken.Length, 0);
7452 _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
7453 _physicalStateObj._pendingData = true;
7454 _physicalStateObj._messageStatus = 0;
7456 _connHandler._federatedAuthenticationRequested = true;
7459 private void SSPIData(byte[] receivedBuff, UInt32 receivedLength, byte[] sendBuff, ref UInt32 sendLength) {
7460 SNISSPIData(receivedBuff, receivedLength, sendBuff, ref sendLength);
7463 private void SNISSPIData(byte[] receivedBuff, UInt32 receivedLength, byte[] sendBuff, ref UInt32 sendLength)
7465 if (receivedBuff == null)
7467 // we do not have SSPI data coming from server, so send over 0's for pointer and length
7468 receivedLength = 0;
7470 // we need to respond to the server's message with SSPI data
7471 if(0 != SNINativeMethodWrapper.SNISecGenClientContext(_physicalStateObj.Handle, receivedBuff, receivedLength, sendBuff, ref sendLength, _sniSpnBuffer))
7473 SSPIError(SQLMessage.SSPIGenerateError(), TdsEnums.GEN_CLIENT_CONTEXT);
7478 private void ProcessSSPI(int receivedLength) {
7479 SniContext outerContext=_physicalStateObj.SniContext;
7480 _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi;
7481 // allocate received buffer based on length from SSPI message
7482 byte[] receivedBuff = new byte[receivedLength];
7484 // read SSPI data received from server
7485 Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
7486 bool result = _physicalStateObj.TryReadByteArray(receivedBuff, 0, receivedLength);
7487 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
7489 // allocate send buffer and initialize length
7490 byte[] sendBuff = new byte[s_maxSSPILength];
7491 UInt32 sendLength = s_maxSSPILength;
7493 // make call for SSPI data
7494 SSPIData(receivedBuff, (UInt32)receivedLength, sendBuff, ref sendLength);
7496 // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA!
7497 _physicalStateObj.WriteByteArray(sendBuff, (int)sendLength, 0);
7499 // set message type so server knows its a SSPI response
7500 _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI;
7502 // send to server
7503 _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
7504 _physicalStateObj.SniContext=outerContext;
7507 private void SSPIError(string error, string procedure) {
7508 Debug.Assert(!ADP.IsEmpty(procedure), "TdsParser.SSPIError called with an empty or null procedure string");
7509 Debug.Assert(!ADP.IsEmpty(error), "TdsParser.SSPIError called with an empty or null error string");
7511 _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _server, error, procedure, 0));
7512 ThrowExceptionAndWarning(_physicalStateObj);
7515 private void LoadSSPILibrary() {
7516 // Outer check so we don't acquire lock once once it's loaded.
7517 if (!s_fSSPILoaded) {
7518 lock (s_tdsParserLock) {
7519 // re-check inside lock
7520 if (!s_fSSPILoaded) {
7521 // use local for ref param to defer setting s_maxSSPILength until we know the call succeeded.
7522 UInt32 maxLength = 0;
7523 if (0 != SNINativeMethodWrapper.SNISecInitPackage(ref maxLength))
7524 SSPIError(SQLMessage.SSPIInitializeError(), TdsEnums.INIT_SSPI_PACKAGE);
7526 s_maxSSPILength = maxLength;
7527 s_fSSPILoaded = true;
7533 if (s_maxSSPILength > Int32.MaxValue) {
7534 throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503
7538 private void LoadADALLibrary() {
7539 // Outer check so we don't acquire lock once once it's loaded.
7540 if (!s_fADALLoaded) {
7541 lock (s_tdsParserLock) {
7542 // re-check inside lock
7543 if (!s_fADALLoaded) {
7544 int result = ADALNativeWrapper.ADALInitialize();
7546 if (0 == result) {
7547 s_fADALLoaded = true;
7549 else {
7550 s_fADALLoaded = false;
7552 SqlAuthenticationMethod authentication = SqlAuthenticationMethod.NotSpecified;
7554 if (_connHandler.ConnectionOptions != null)
7556 authentication = _connHandler.ConnectionOptions.Authentication;
7558 // Only the below connection string options should have ended up calling this function.
7559 Debug.Assert(authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword);
7562 _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _server, Res.GetString(Res.SQL_ADALInitializeError, authentication.ToString("G"), result.ToString("X")), TdsEnums.INIT_ADAL_PACKAGE, 0));
7564 ThrowExceptionAndWarning(_physicalStateObj);
7571 internal byte[] GetDTCAddress(int timeout, TdsParserStateObject stateObj) {
7572 // If this fails, the server will return a server error - Sameet Agarwal confirmed.
7573 // Success: DTCAddress returned. Failure: SqlError returned.
7575 byte[] dtcAddr = null;
7577 using (SqlDataReader dtcReader = TdsExecuteTransactionManagerRequest(
7578 null,
7579 TdsEnums.TransactionManagerRequestType.GetDTCAddress,
7580 null,
7581 TdsEnums.TransactionManagerIsolationLevel.Unspecified,
7582 timeout, null, stateObj, true)) {
7584 Debug.Assert(SniContext.Snix_Read==stateObj.SniContext, String.Format((IFormatProvider)null, "The SniContext should be Snix_Read but it actually is {0}", stateObj.SniContext));
7585 if (null != dtcReader && dtcReader.Read()) {
7586 Debug.Assert(dtcReader.GetName(0) == "TM Address", "TdsParser: GetDTCAddress did not return 'TM Address'");
7588 // DTCAddress is of variable size, and does not have a maximum. So we call GetBytes
7589 // to get the length of the dtcAddress, then allocate a byte array of that length,
7590 // then call GetBytes again on that byte[] with the length
7591 long dtcLength = dtcReader.GetBytes(0, 0, null, 0, 0);
7594 if (dtcLength <= Int32.MaxValue) {
7595 int cb = (int)dtcLength;
7597 dtcAddr = new byte[cb];
7598 dtcReader.GetBytes(0, 0, dtcAddr, 0, cb);
7600 #if DEBUG
7601 else {
7602 Debug.Assert(false, "unexpected length (> Int32.MaxValue) returned from dtcReader.GetBytes");
7603 // if we hit this case we'll just return a null address so that the user
7604 // will get a transcaction enlistment error in the upper layers
7606 #endif
7609 return dtcAddr;
7612 // Propagate the dtc cookie to the server, enlisting the connection.
7613 internal void PropagateDistributedTransaction(byte[] buffer, int timeout, TdsParserStateObject stateObj) {
7614 // if this fails, the server will return a server error - Sameet Agarwal confirmed
7615 // Success: server will return done token. Failure: SqlError returned.
7617 TdsExecuteTransactionManagerRequest(buffer,
7618 TdsEnums.TransactionManagerRequestType.Propagate, null,
7619 TdsEnums.TransactionManagerIsolationLevel.Unspecified, timeout, null, stateObj, true);
7622 internal SqlDataReader TdsExecuteTransactionManagerRequest(
7623 byte[] buffer,
7624 TdsEnums.TransactionManagerRequestType request,
7625 string transactionName,
7626 TdsEnums.TransactionManagerIsolationLevel isoLevel,
7627 int timeout,
7628 SqlInternalTransaction transaction,
7629 TdsParserStateObject stateObj,
7630 bool isDelegateControlRequest) {
7632 Debug.Assert(this == stateObj.Parser, "different parsers");
7634 if (TdsParserState.Broken == State || TdsParserState.Closed == State) {
7635 return null;
7638 // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
7639 // delegated transactions often happen while there is an open result
7640 // set, so we need to handle them by using a different MARS session,
7641 // otherwise we'll write on the physical state objects while someone
7642 // else is using it. When we don't have MARS enabled, we need to
7643 // lock the physical state object to syncronize it's use at least
7644 // until we increment the open results count. Once it's been
7645 // incremented the delegated transaction requests will fail, so they
7646 // won't stomp on anything.
7649 Debug.Assert(!_connHandler.ThreadHasParserLockForClose || _connHandler._parserLock.ThreadMayHaveLock(), "Thread claims to have parser lock, but lock is not taken");
7650 bool callerHasConnectionLock = _connHandler.ThreadHasParserLockForClose; // If the thread already claims to have the parser lock, then we will let the caller handle releasing it
7651 if (!callerHasConnectionLock) {
7652 _connHandler._parserLock.Wait(canReleaseFromAnyThread:false);
7653 _connHandler.ThreadHasParserLockForClose = true;
7655 // Capture _asyncWrite (after taking lock) to restore it afterwards
7656 bool hadAsyncWrites = _asyncWrite;
7657 try {
7658 // Temprarily disable async writes
7659 _asyncWrite = false;
7661 // This validation step MUST be done after locking the connection to guarantee we don't
7662 // accidentally execute after the transaction has completed on a different thread.
7663 if (!isDelegateControlRequest) {
7664 _connHandler.CheckEnlistedTransactionBinding();
7667 stateObj._outputMessageType = TdsEnums.MT_TRANS; // set message type
7668 stateObj.SetTimeoutSeconds(timeout);
7670 stateObj.SniContext = SniContext.Snix_Execute;
7672 if (_isYukon) {
7673 const int marsHeaderSize = 18; // 4 + 2 + 8 + 4
7674 const int totalHeaderLength = 22; // 4 + 4 + 2 + 8 + 4
7675 Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length");
7676 // Write total header length
7677 WriteInt(totalHeaderLength, stateObj);
7678 // Write mars header length
7679 WriteInt(marsHeaderSize, stateObj);
7680 WriteMarsHeaderData(stateObj, _currentTransaction);
7683 WriteShort((short)request, stateObj); // write TransactionManager Request type
7685 bool returnReader = false;
7687 switch (request) {
7688 case TdsEnums.TransactionManagerRequestType.GetDTCAddress:
7689 WriteShort(0, stateObj);
7691 returnReader = true;
7692 break;
7693 case TdsEnums.TransactionManagerRequestType.Propagate:
7694 if (null != buffer) {
7695 WriteShort(buffer.Length, stateObj);
7696 stateObj.WriteByteArray(buffer, buffer.Length, 0);
7698 else {
7699 WriteShort(0, stateObj);
7701 break;
7702 case TdsEnums.TransactionManagerRequestType.Begin:
7703 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for BeginTransaction!");
7704 Debug.Assert(null != transaction, "Should have specified an internalTransaction when doing a BeginTransaction request!");
7706 // Only assign the passed in transaction if it is not equal to the current transaction.
7707 // And, if it is not equal, the current actually should be null. Anything else
7708 // is a unexpected state. The concern here is mainly for the mixed use of
7709 // T-SQL and API transactions. See SQL BU DT 345300 for full details and repro.
7711 // Expected states:
7712 // 1) _pendingTransaction = null, _currentTransaction = null, non null transaction
7713 // passed in on BeginTransaction API call.
7714 // 2) _currentTransaction != null, _pendingTransaction = null, non null transaction
7715 // passed in but equivalent to _currentTransaction.
7717 // #1 will occur on standard BeginTransactionAPI call. #2 should only occur if
7718 // t-sql transaction started followed by a call to SqlConnection.BeginTransaction.
7719 // Any other state is unknown.
7720 if (_currentTransaction != transaction) {
7721 Debug.Assert(_currentTransaction == null || true == _fResetConnection, "We should not have a current Tx at this point");
7722 PendingTransaction = transaction;
7725 stateObj.WriteByte((byte)isoLevel);
7727 stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string).
7728 WriteString(transactionName, stateObj);
7729 break;
7730 case TdsEnums.TransactionManagerRequestType.Promote:
7731 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for PromoteTransaction!");
7732 // No payload - except current transaction in header
7733 // Promote returns a DTC cookie. However, the transaction cookie we use for the
7734 // connection does not change after a promote.
7735 break;
7736 case TdsEnums.TransactionManagerRequestType.Commit:
7737 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for CommitTransaction!");
7739 Debug.Assert(transactionName.Length == 0, "Should not have a transaction name on Commit");
7740 stateObj.WriteByte((byte)0); // No xact name
7742 stateObj.WriteByte(0); // No flags
7744 Debug.Assert(isoLevel == TdsEnums.TransactionManagerIsolationLevel.Unspecified, "Should not have isolation level other than unspecified on Commit!");
7745 // WriteByte((byte) 0, stateObj); // IsolationLevel
7746 // WriteByte((byte) 0, stateObj); // No begin xact name
7747 break;
7748 case TdsEnums.TransactionManagerRequestType.Rollback:
7749 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for RollbackTransaction!");
7751 stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string).
7752 WriteString(transactionName, stateObj);
7754 stateObj.WriteByte(0); // No flags
7756 Debug.Assert(isoLevel == TdsEnums.TransactionManagerIsolationLevel.Unspecified, "Should not have isolation level other than unspecified on Commit!");
7757 // WriteByte((byte) 0, stateObj); // IsolationLevel
7758 // WriteByte((byte) 0, stateObj); // No begin xact name
7759 break;
7760 case TdsEnums.TransactionManagerRequestType.Save:
7761 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for SaveTransaction!");
7763 stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string).
7764 WriteString(transactionName, stateObj);
7765 break;
7766 default:
7767 Debug.Assert(false, "Unexpected TransactionManagerRequest");
7768 break;
7771 Task writeTask = stateObj.WritePacket(TdsEnums.HARDFLUSH);
7772 Debug.Assert(writeTask == null, "Writes should not pend when writing sync");
7773 stateObj._pendingData = true;
7774 stateObj._messageStatus = 0;
7776 SqlDataReader dtcReader = null;
7777 stateObj.SniContext = SniContext.Snix_Read;
7778 if (returnReader) {
7779 dtcReader = new SqlDataReader(null, CommandBehavior.Default);
7780 Debug.Assert(this == stateObj.Parser, "different parser");
7781 #if DEBUG
7782 // Remove the current owner of stateObj - otherwise we will hit asserts
7783 stateObj.Owner = null;
7784 #endif
7785 dtcReader.Bind(stateObj);
7787 // force consumption of metadata
7788 _SqlMetaDataSet metaData = dtcReader.MetaData;
7790 else {
7791 Run(RunBehavior.UntilDone, null, null, null, stateObj);
7794 // If the retained ID is no longer valid (because we are enlisting in null or a new transaction) then it should be cleared
7795 if (((request == TdsEnums.TransactionManagerRequestType.Begin) || (request == TdsEnums.TransactionManagerRequestType.Propagate)) && ((transaction == null) || (transaction.TransactionId != _retainedTransactionId))) {
7796 _retainedTransactionId = SqlInternalTransaction.NullTransactionId;
7799 return dtcReader;
7801 catch (Exception e) {
7803 if (!ADP.IsCatchableExceptionType(e)) {
7804 throw;
7807 FailureCleanup(stateObj, e);
7809 throw;
7811 finally {
7812 // SQLHotfix 50000518
7813 // make sure we don't leave temporary fields set when leaving this function
7814 _pendingTransaction = null;
7816 _asyncWrite = hadAsyncWrites;
7818 if (!callerHasConnectionLock) {
7819 _connHandler.ThreadHasParserLockForClose = false;
7820 _connHandler._parserLock.Release();
7825 internal void FailureCleanup(TdsParserStateObject stateObj, Exception e) {
7826 int old_outputPacketNumber = stateObj._outputPacketNumber;
7828 if (Bid.TraceOn) {
7829 Bid.Trace("<sc.TdsParser.FailureCleanup|ERR> Exception caught on ExecuteXXX: '%ls' \n", e.ToString());
7832 if (stateObj.HasOpenResult) { // SQL BU DT 383773 - need to decrement openResultCount if operation failed.
7833 stateObj.DecrementOpenResultCount();
7836 // be sure to wipe out our buffer if we started sending stuff
7837 stateObj.ResetBuffer();
7838 stateObj._outputPacketNumber = 1; // end of message - reset to 1 - per ramas
7840 if (old_outputPacketNumber != 1 && _state == TdsParserState.OpenLoggedIn) {
7841 Debug.Assert(_connHandler._parserLock.ThreadMayHaveLock(), "Should not be calling into FailureCleanup without first taking the parser lock");
7843 bool originalThreadHasParserLock = _connHandler.ThreadHasParserLockForClose;
7844 try {
7845 // Dev11 Bug 385286 : ExecuteNonQueryAsync hangs when trying to write a parameter which generates ArgumentException and while handling that exception the server disconnects the connection
7846 // Need to set this to true such that if we have an error sending\processing the attention, we won't deadlock ourselves
7847 _connHandler.ThreadHasParserLockForClose = true;
7849 // If _outputPacketNumber prior to ResetBuffer was not equal to 1, a packet was already
7850 // sent to the server and so we need to send an attention and process the attention ack.
7851 stateObj.SendAttention();
7852 ProcessAttention(stateObj);
7854 finally {
7855 // Reset the ThreadHasParserLock value incase our caller expects it to be set\not set
7856 _connHandler.ThreadHasParserLockForClose = originalThreadHasParserLock;
7860 Bid.Trace("<sc.TdsParser.FailureCleanup|ERR> Exception rethrown. \n");
7863 internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool sync, bool callerHasConnectionLock = false) {
7864 if (TdsParserState.Broken == State || TdsParserState.Closed == State) {
7865 return null;
7868 if (stateObj.BcpLock) {
7869 throw SQL.ConnectionLockedForBcpEvent();
7872 // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
7873 // delegated transactions often happen while there is an open result
7874 // set, so we need to handle them by using a different MARS session,
7875 // otherwise we'll write on the physical state objects while someone
7876 // else is using it. When we don't have MARS enabled, we need to
7877 // lock the physical state object to syncronize it's use at least
7878 // until we increment the open results count. Once it's been
7879 // incremented the delegated transaction requests will fail, so they
7880 // won't stomp on anything.
7882 // Only need to take the lock if neither the thread nor the caller claims to already have it
7883 bool needToTakeParserLock = (!callerHasConnectionLock) && (!_connHandler.ThreadHasParserLockForClose);
7884 Debug.Assert(!_connHandler.ThreadHasParserLockForClose || sync, "Thread shouldn't claim to have the parser lock if we are doing async writes"); // Since we have the possibility of pending with async writes, make sure the thread doesn't claim to already have the lock
7885 Debug.Assert(needToTakeParserLock || _connHandler._parserLock.ThreadMayHaveLock(), "Thread or caller claims to have connection lock, but lock is not taken");
7887 bool releaseConnectionLock = false;
7888 if (needToTakeParserLock) {
7889 _connHandler._parserLock.Wait(canReleaseFromAnyThread: !sync);
7890 releaseConnectionLock = true;
7893 // Switch the writing mode
7894 // NOTE: We are not turning off async writes when we complete since SqlBulkCopy uses this method and expects _asyncWrite to not change
7895 _asyncWrite = !sync;
7897 try {
7898 // Check that the connection is still alive
7899 if ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken)) {
7900 throw ADP.ClosedConnectionError();
7903 // This validation step MUST be done after locking the connection to guarantee we don't
7904 // accidentally execute after the transaction has completed on a different thread.
7905 _connHandler.CheckEnlistedTransactionBinding();
7907 stateObj.SetTimeoutSeconds(timeout);
7908 if ((!_fMARS) && (_physicalStateObj.HasOpenResult))
7910 Bid.Trace("<sc.TdsParser.TdsExecuteSQLBatch|ERR> Potential multi-threaded misuse of connection, non-MARs connection with an open result %d#\n", ObjectID);
7912 stateObj.SniContext = SniContext.Snix_Execute;
7914 if (_isYukon) {
7916 WriteRPCBatchHeaders(stateObj, notificationRequest);
7919 stateObj._outputMessageType = TdsEnums.MT_SQL;
7921 WriteString(text, text.Length, 0, stateObj);
7923 Task executeTask = stateObj.ExecuteFlush();
7924 if (executeTask == null) {
7925 stateObj.SniContext = SniContext.Snix_Read;
7927 else {
7928 Debug.Assert(!sync, "Should not have gotten a Task when writing in sync mode");
7930 // Need to wait for flush - continuation will unlock the connection
7931 bool taskReleaseConnectionLock = releaseConnectionLock;
7932 releaseConnectionLock = false;
7933 return executeTask.ContinueWith(t => {
7934 Debug.Assert(!t.IsCanceled, "Task should not be canceled");
7935 try {
7936 if (t.IsFaulted) {
7937 FailureCleanup(stateObj, t.Exception.InnerException);
7938 throw t.Exception.InnerException;
7940 else {
7941 stateObj.SniContext = SniContext.Snix_Read;
7944 finally {
7945 if (taskReleaseConnectionLock) {
7946 _connHandler._parserLock.Release();
7949 }, TaskScheduler.Default);
7952 // Finished sync
7953 return null;
7955 catch (Exception e) {
7956 //Debug.Assert(_state == TdsParserState.Broken, "Caught exception in TdsExecuteSQLBatch but connection was not broken!");
7958 if (!ADP.IsCatchableExceptionType(e)) {
7959 throw;
7962 FailureCleanup(stateObj, e);
7964 throw;
7966 finally {
7967 if (releaseConnectionLock) {
7968 _connHandler._parserLock.Release();
7973 internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync = true,
7974 TaskCompletionSource<object> completion = null, int startRpc = 0, int startParam = 0) {
7975 bool firstCall = (completion == null);
7976 bool releaseConnectionLock = false;
7978 Debug.Assert(cmd != null, @"cmd cannot be null inside TdsExecuteRPC");
7979 Debug.Assert(!firstCall || startRpc == 0, "startRpc is not 0 on first call");
7980 Debug.Assert(!firstCall || startParam == 0, "startParam is not 0 on first call");
7981 Debug.Assert(!firstCall || !_connHandler.ThreadHasParserLockForClose, "Thread should not already have connection lock");
7982 Debug.Assert(firstCall || _connHandler._parserLock.ThreadMayHaveLock(), "Connection lock not taken after the first call");
7983 try {
7984 _SqlRPC rpcext = null;
7985 int tempLen;
7987 // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
7988 // delegated transactions often happen while there is an open result
7989 // set, so we need to handle them by using a different MARS session,
7990 // otherwise we'll write on the physical state objects while someone
7991 // else is using it. When we don't have MARS enabled, we need to
7992 // lock the physical state object to syncronize it's use at least
7993 // until we increment the open results count. Once it's been
7994 // incremented the delegated transaction requests will fail, so they
7995 // won't stomp on anything.
7998 if (firstCall) {
7999 _connHandler._parserLock.Wait(canReleaseFromAnyThread:!sync);
8000 releaseConnectionLock = true;
8002 try {
8003 // Ensure that connection is alive
8004 if ((TdsParserState.Broken == State) || (TdsParserState.Closed == State)) {
8005 throw ADP.ClosedConnectionError();
8008 // This validation step MUST be done after locking the connection to guarantee we don't
8009 // accidentally execute after the transaction has completed on a different thread.
8010 if (firstCall) {
8011 _asyncWrite = !sync;
8013 _connHandler.CheckEnlistedTransactionBinding();
8015 stateObj.SetTimeoutSeconds(timeout);
8016 if ((!_fMARS) && (_physicalStateObj.HasOpenResult))
8018 Bid.Trace("<sc.TdsParser.TdsExecuteRPC|ERR> Potential multi-threaded misuse of connection, non-MARs connection with an open result %d#\n", ObjectID);
8020 stateObj.SniContext = SniContext.Snix_Execute;
8022 if (_isYukon) {
8024 WriteRPCBatchHeaders(stateObj, notificationRequest);
8027 stateObj._outputMessageType = TdsEnums.MT_RPC;
8030 for (int ii = startRpc; ii < rpcArray.Length; ii++) {
8031 rpcext = rpcArray[ii];
8033 if (startParam == 0 || ii > startRpc) {
8034 if (rpcext.ProcID != 0 && _isShiloh) {
8035 // Perf optimization for Shiloh and later,
8036 Debug.Assert(rpcext.ProcID < 255, "rpcExec:ProcID can't be larger than 255");
8037 WriteShort(0xffff, stateObj);
8038 WriteShort((short)(rpcext.ProcID), stateObj);
8040 else {
8041 Debug.Assert(!ADP.IsEmpty(rpcext.rpcName), "must have an RPC name");
8042 tempLen = rpcext.rpcName.Length;
8043 WriteShort(tempLen, stateObj);
8044 WriteString(rpcext.rpcName, tempLen, 0, stateObj);
8047 // Options
8048 WriteShort((short)rpcext.options, stateObj);
8051 // Stream out parameters
8052 SqlParameter[] parameters = rpcext.parameters;
8054 for (int i = (ii == startRpc) ? startParam : 0; i < parameters.Length; i++) {
8055 // Debug.WriteLine("i: " + i.ToString(CultureInfo.InvariantCulture));
8056 // parameters can be unnamed
8057 SqlParameter param = parameters[i];
8058 // Since we are reusing the parameters array, we cannot rely on length to indicate no of parameters.
8059 if (param == null)
8060 break; // End of parameters for this execute
8062 // Throw an exception if ForceColumnEncryption is set on a parameter and the ColumnEncryption is not enabled on SqlConnection or SqlCommand
8063 if (param.ForceColumnEncryption &&
8064 !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled ||
8065 (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled))) {
8066 throw SQL.ParamInvalidForceColumnEncryptionSetting(param.ParameterName, rpcext.GetCommandTextOrRpcName());
8069 // Check if the applications wants to force column encryption to avoid sending sensitive data to server
8070 if (param.ForceColumnEncryption && param.CipherMetadata == null
8071 && (param.Direction == ParameterDirection.Input || param.Direction == ParameterDirection.InputOutput)) {
8072 // Application wants a parameter to be encrypted before sending it to server, however server doesnt think this parameter needs encryption.
8073 throw SQL.ParamUnExpectedEncryptionMetadata(param.ParameterName, rpcext.GetCommandTextOrRpcName());
8076 // Validate parameters are not variable length without size and with null value. MDAC 66522
8077 param.Validate(i, isCommandProc);
8079 // type (parameter record stores the MetaType class which is a helper that encapsulates all the type information we need here)
8080 MetaType mt = param.InternalMetaType;
8082 if (mt.IsNewKatmaiType) {
8083 WriteSmiParameter(param, i, 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_DEFAULT), stateObj);
8084 continue;
8087 if ((!_isShiloh && !mt.Is70Supported) ||
8088 (!_isYukon && !mt.Is80Supported) ||
8089 (!_isKatmai && !mt.Is90Supported)) {
8090 throw ADP.VersionDoesNotSupportDataType(mt.TypeName);
8092 object value = null;
8093 bool isNull = true;
8094 bool isSqlVal = false;
8095 bool isDataFeed = false;
8096 // if we have an output param, set the value to null so we do not send it across to the server
8097 if (param.Direction == ParameterDirection.Output) {
8098 isSqlVal = param.ParamaterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the paramater we will no longer be able to distinguish what type were seeing.
8099 param.Value = null;
8100 param.ParamaterIsSqlType = isSqlVal;
8102 else {
8103 value = param.GetCoercedValue();
8104 isNull = param.IsNull;
8105 if (!isNull) {
8106 isSqlVal = param.CoercedValueIsSqlType;
8107 isDataFeed = param.CoercedValueIsDataFeed;
8111 WriteParameterName(param.ParameterNameFixed, stateObj);
8113 // Write parameter status
8114 stateObj.WriteByte(rpcext.paramoptions[i]);
8116 // MaxLen field is only written out for non-fixed length data types
8117 // use the greater of the two sizes for maxLen
8118 int actualSize;
8119 int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize();
8121 //for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation
8122 if (mt.TDSType != TdsEnums.SQLUDT)
8123 // getting the actualSize is expensive, cache here and use below
8124 actualSize = param.GetActualSize();
8125 else
8126 actualSize = 0; //get this later
8128 byte precision = 0;
8129 byte scale = 0;
8131 // scale and precision are only relevant for numeric and decimal types
8132 // adjust the actual value scale and precision to match the user specified
8133 if (mt.SqlDbType == SqlDbType.Decimal) {
8134 precision = param.GetActualPrecision();
8135 scale = param.GetActualScale();
8137 if (precision > TdsEnums.MAX_NUMERIC_PRECISION) {
8138 throw SQL.PrecisionValueOutOfRange(precision);
8141 // bug 49512, make sure the value matches the scale the user enters
8142 if (!isNull) {
8143 if (isSqlVal) {
8144 value = AdjustSqlDecimalScale((SqlDecimal)value, scale);
8146 // If Precision is specified, verify value precision vs param precision
8147 if (precision != 0) {
8148 if (precision < ((SqlDecimal)value).Precision) {
8149 throw ADP.ParameterValueOutOfRange((SqlDecimal)value);
8153 else {
8154 value = AdjustDecimalScale((Decimal)value, scale);
8156 SqlDecimal sqlValue = new SqlDecimal((Decimal)value);
8158 // If Precision is specified, verify value precision vs param precision
8159 if (precision != 0) {
8160 if (precision < sqlValue.Precision) {
8161 throw ADP.ParameterValueOutOfRange((Decimal)value);
8168 bool isParameterEncrypted = 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_ENCRYPTED);
8170 // Additional information we need to send over wire to the server when writing encrypted parameters.
8171 SqlColumnEncryptionInputParameterInfo encryptedParameterInfoToWrite = null;
8173 // If the parameter is encrypted, we need to encrypt the value.
8174 if (isParameterEncrypted) {
8175 Debug.Assert(mt.TDSType != TdsEnums.SQLVARIANT &&
8176 mt.TDSType != TdsEnums.SQLUDT &&
8177 mt.TDSType != TdsEnums.SQLXMLTYPE &&
8178 mt.TDSType != TdsEnums.SQLIMAGE &&
8179 mt.TDSType != TdsEnums.SQLTEXT &&
8180 mt.TDSType != TdsEnums.SQLNTEXT, "Type unsupported for encryption");
8182 byte[] serializedValue = null;
8183 byte[] encryptedValue = null;
8185 if (!isNull) {
8186 try {
8187 if (isSqlVal) {
8188 serializedValue = SerializeUnencryptedSqlValue(value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj);
8190 else {
8191 // for codePageEncoded types, WriteValue simply expects the number of characters
8192 // For plp types, we also need the encoded byte size
8193 serializedValue = SerializeUnencryptedValue(value, mt, param.GetActualScale(), actualSize, param.Offset, isDataFeed, param.NormalizationRuleVersion, stateObj);
8196 Debug.Assert(serializedValue != null, "serializedValue should not be null in TdsExecuteRPC.");
8197 encryptedValue = SqlSecurityUtility.EncryptWithKey(serializedValue, param.CipherMetadata, _connHandler.ConnectionOptions.DataSource);
8199 catch (Exception e) {
8200 throw SQL.ParamEncryptionFailed(param.ParameterName, null, e);
8203 Debug.Assert(encryptedValue != null && encryptedValue.Length > 0,
8204 "encryptedValue should not be null or empty in TdsExecuteRPC.");
8206 else {
8207 encryptedValue = null;
8210 // Change the datatype to varbinary(max).
8211 // Since we don't know the size of the encrypted parameter on the server side, always set to (max).
8213 mt = MetaType.MetaMaxVarBinary;
8214 size = -1;
8215 actualSize = (encryptedValue == null) ? 0 : encryptedValue.Length;
8217 encryptedParameterInfoToWrite = new SqlColumnEncryptionInputParameterInfo(param.GetMetadataForTypeInfo(),
8218 param.CipherMetadata);
8220 // Set the value to the encrypted value and mark isSqlVal as false for VARBINARY encrypted value.
8221 value = encryptedValue;
8222 isSqlVal = false;
8225 Debug.Assert(isParameterEncrypted == (encryptedParameterInfoToWrite != null),
8226 "encryptedParameterInfoToWrite can be not null if and only if isParameterEncrypted is true.");
8228 Debug.Assert(!isSqlVal || !isParameterEncrypted, "isParameterEncrypted can be true only if isSqlVal is false.");
8231 // fixup the types by using the NullableType property of the MetaType class
8233 // following rules should be followed based on feedback from the M-SQL team
8234 // 1) always use the BIG* types (ex: instead of SQLCHAR use SQLBIGCHAR)
8235 // 2) always use nullable types (ex: instead of SQLINT use SQLINTN)
8236 // 3) DECIMALN should always be sent as NUMERICN
8238 stateObj.WriteByte(mt.NullableType);
8240 // handle variants here: the SQLVariant writing routine will write the maxlen and actual len columns
8241 if (mt.TDSType == TdsEnums.SQLVARIANT) {
8242 // devnote: Do we ever hit this codepath? Yes, when a null value is being writen out via a sql variant
8243 // param.GetActualSize is not used
8244 WriteSqlVariantValue(isSqlVal ? MetaType.GetComValueFromSqlVariant(value) : value, param.GetActualSize(), param.Offset, stateObj);
8245 continue;
8248 int codePageByteSize = 0;
8249 int maxsize = 0;
8251 if (mt.IsAnsiType) {
8252 // Avoid the following code block if ANSI but unfilled LazyMat blob
8253 if ((!isNull) && (!isDataFeed)) {
8254 string s;
8256 if (isSqlVal) {
8257 if (value is SqlString) {
8258 s = ((SqlString)value).Value;
8260 else {
8261 Debug.Assert(value is SqlChars, "Unknown value for Ansi datatype");
8262 s = new String(((SqlChars)value).Value);
8265 else {
8266 s = (string)value;
8269 codePageByteSize = GetEncodingCharLength(s, actualSize, param.Offset, _defaultEncoding);
8272 if (mt.IsPlp) {
8273 WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj);
8275 else {
8276 maxsize = (size > codePageByteSize) ? size : codePageByteSize;
8277 if (maxsize == 0) {
8278 // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322)
8279 if (mt.IsNCharType)
8280 maxsize = 2;
8281 else
8282 maxsize = 1;
8285 WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj);
8288 else {
8289 // If type timestamp - treat as fixed type and always send over timestamp length, which is 8.
8290 // For fixed types, we either send null or fixed length for type length. We want to match that
8291 // behavior for timestamps. However, in the case of null, we still must send 8 because if we
8292 // send null we will not receive a output val. You can send null for fixed types and still
8293 // receive a output value, but not for variable types. So, always send 8 for timestamp because
8294 // while the user sees it as a fixed type, we are actually representing it as a bigbinary which
8295 // is variable.
8296 if (mt.SqlDbType == SqlDbType.Timestamp) {
8297 WriteParameterVarLen(mt, TdsEnums.TEXT_TIME_STAMP_LEN, false, stateObj);
8299 else if (mt.SqlDbType == SqlDbType.Udt) {
8300 byte[] udtVal = null;
8301 Microsoft.SqlServer.Server.Format format = Microsoft.SqlServer.Server.Format.Native;
8303 Debug.Assert(_isYukon, "Invalid DataType UDT for non-Yukon or later server!");
8305 if (!isNull) {
8306 udtVal = _connHandler.Connection.GetBytes(value, out format, out maxsize);
8308 Debug.Assert(null != udtVal, "GetBytes returned null instance. Make sure that it always returns non-null value");
8309 size = udtVal.Length;
8311 //it may be legitimate, but we dont support it yet
8312 if (size < 0 || (size >= UInt16.MaxValue && maxsize != -1))
8313 throw new IndexOutOfRangeException();
8316 //if this is NULL value, write special null value
8317 byte[] lenBytes = BitConverter.GetBytes((Int64)size);
8319 if (ADP.IsEmpty(param.UdtTypeName))
8320 throw SQL.MustSetUdtTypeNameForUdtParams();
8322 // Split the input name. TypeName is returned as single 3 part name during DeriveParameters.
8323 // NOTE: ParseUdtTypeName throws if format is incorrect
8324 String[] names = SqlParameter.ParseTypeName(param.UdtTypeName, true /* is UdtTypeName */);
8325 if (!ADP.IsEmpty(names[0]) && TdsEnums.MAX_SERVERNAME < names[0].Length) {
8326 throw ADP.ArgumentOutOfRange("names");
8328 if (!ADP.IsEmpty(names[1]) && TdsEnums.MAX_SERVERNAME < names[names.Length - 2].Length) {
8329 throw ADP.ArgumentOutOfRange("names");
8331 if (TdsEnums.MAX_SERVERNAME < names[2].Length) {
8332 throw ADP.ArgumentOutOfRange("names");
8335 WriteUDTMetaData(value, names[0], names[1], names[2], stateObj);
8338 if (!isNull) {
8339 WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length
8340 if (udtVal.Length > 0) { // Only write chunk length if its value is greater than 0
8341 WriteInt(udtVal.Length, stateObj); // Chunk length
8342 stateObj.WriteByteArray(udtVal, udtVal.Length, 0); // Value
8344 WriteInt(0, stateObj); // Terminator
8346 else {
8347 WriteUnsignedLong(TdsEnums.SQL_PLP_NULL, stateObj); // PLP Null.
8349 continue; // End of UDT - continue to next parameter.
8352 else if (mt.IsPlp) {
8353 if (mt.SqlDbType != SqlDbType.Xml)
8354 WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj);
8356 else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) { // Time, Date, DateTime2, DateTimeoffset do not have the size written out
8357 maxsize = (size > actualSize) ? size : actualSize;
8358 if (maxsize == 0 && IsYukonOrNewer) {
8359 // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322)
8360 if (mt.IsNCharType)
8361 maxsize = 2;
8362 else
8363 maxsize = 1;
8366 WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj);
8370 // scale and precision are only relevant for numeric and decimal types
8371 if (mt.SqlDbType == SqlDbType.Decimal) {
8372 if (0 == precision) {
8373 if (_isShiloh)
8374 stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION);
8375 else
8376 stateObj.WriteByte(TdsEnums.SPHINX_DEFAULT_NUMERIC_PRECISION);
8378 else
8379 stateObj.WriteByte(precision);
8381 stateObj.WriteByte(scale);
8383 else if (mt.IsVarTime) {
8384 stateObj.WriteByte(param.GetActualScale());
8387 // write out collation or xml metadata
8389 if (_isYukon && (mt.SqlDbType == SqlDbType.Xml)) {
8390 if (((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) ||
8391 ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) ||
8392 ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty))) {
8393 stateObj.WriteByte(1); //Schema present flag
8395 if ((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) {
8396 tempLen = (param.XmlSchemaCollectionDatabase).Length;
8397 stateObj.WriteByte((byte)(tempLen));
8398 WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj);
8400 else {
8401 stateObj.WriteByte(0); // No dbname
8404 if ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) {
8405 tempLen = (param.XmlSchemaCollectionOwningSchema).Length;
8406 stateObj.WriteByte((byte)(tempLen));
8407 WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj);
8409 else {
8410 stateObj.WriteByte(0); // no xml schema name
8412 if ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty)) {
8413 tempLen = (param.XmlSchemaCollectionName).Length;
8414 WriteShort((short)(tempLen), stateObj);
8415 WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj);
8417 else {
8418 WriteShort(0, stateObj); // No xml schema collection name
8422 else {
8423 stateObj.WriteByte(0); // No schema
8426 else if (_isShiloh && mt.IsCharType) {
8427 // if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter
8428 SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation;
8429 Debug.Assert(_defaultCollation != null, "_defaultCollation is null!");
8431 WriteUnsignedInt(outCollation.info, stateObj);
8432 stateObj.WriteByte(outCollation.sortId);
8435 if (0 == codePageByteSize)
8436 WriteParameterVarLen(mt, actualSize, isNull, stateObj, isDataFeed);
8437 else
8438 WriteParameterVarLen(mt, codePageByteSize, isNull, stateObj, isDataFeed);
8440 Task writeParamTask = null;
8441 // write the value now
8442 if (!isNull) {
8443 if (isSqlVal) {
8444 writeParamTask = WriteSqlValue(value, mt, actualSize, codePageByteSize, param.Offset, stateObj);
8446 else {
8447 // for codePageEncoded types, WriteValue simply expects the number of characters
8448 // For plp types, we also need the encoded byte size
8449 writeParamTask = WriteValue(value, mt, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed);
8453 // Send encryption metadata for encrypted parameters.
8454 if (isParameterEncrypted) {
8455 writeParamTask = WriteEncryptionMetadata(writeParamTask, encryptedParameterInfoToWrite, stateObj);
8458 if (!sync) {
8459 if (writeParamTask == null) {
8460 writeParamTask = stateObj.WaitForAccumulatedWrites();
8463 if (writeParamTask != null) {
8464 Task task = null;
8465 if (completion == null) {
8466 completion = new TaskCompletionSource<object>();
8467 task = completion.Task;
8470 AsyncHelper.ContinueTask(writeParamTask, completion,
8471 () => TdsExecuteRPC(cmd, rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion,
8472 startRpc: ii, startParam: i + 1),
8473 connectionToDoom: _connHandler,
8474 onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj));
8476 // Take care of releasing the locks
8477 if (releaseConnectionLock) {
8478 task.ContinueWith(_ => {
8479 _connHandler._parserLock.Release();
8480 }, TaskScheduler.Default);
8481 releaseConnectionLock = false;
8484 return task;
8487 #if DEBUG
8488 else {
8489 Debug.Assert(writeParamTask == null, "Should not have a task when executing sync");
8491 #endif
8492 } // parameter for loop
8494 // If this is not the last RPC we are sending, add the batch flag
8495 if (ii < (rpcArray.Length - 1)) {
8496 if (_isYukon) {
8497 stateObj.WriteByte(TdsEnums.YUKON_RPCBATCHFLAG);
8500 else {
8501 stateObj.WriteByte(TdsEnums.SHILOH_RPCBATCHFLAG);
8504 } // rpc for loop
8506 Task execFlushTask = stateObj.ExecuteFlush();
8507 Debug.Assert(!sync || execFlushTask == null, "Should not get a task when executing sync");
8508 if (execFlushTask != null) {
8509 Task task = null;
8511 if (completion == null) {
8512 completion = new TaskCompletionSource<object>();
8513 task = completion.Task;
8516 bool taskReleaseConnectionLock = releaseConnectionLock;
8517 execFlushTask.ContinueWith(tsk => ExecuteFlushTaskCallback(tsk, stateObj, completion, taskReleaseConnectionLock), TaskScheduler.Default);
8519 // ExecuteFlushTaskCallback will take care of the locks for us
8520 releaseConnectionLock = false;
8522 return task;
8525 catch (Exception e) {
8527 if (!ADP.IsCatchableExceptionType(e)) {
8528 throw;
8531 FailureCleanup(stateObj, e);
8533 throw;
8535 FinalizeExecuteRPC(stateObj);
8536 if (completion != null) {
8537 completion.SetResult(null);
8539 return null;
8541 catch (Exception e) {
8542 FinalizeExecuteRPC(stateObj);
8543 if (completion != null) {
8544 completion.SetException(e);
8545 return null;
8547 else {
8548 throw e;
8551 finally {
8552 Debug.Assert(firstCall || !releaseConnectionLock, "Shouldn't be releasing locks synchronously after the first call");
8553 if (releaseConnectionLock) {
8554 _connHandler._parserLock.Release();
8559 private void FinalizeExecuteRPC(TdsParserStateObject stateObj) {
8560 stateObj.SniContext = SniContext.Snix_Read;
8561 _asyncWrite = false;
8564 private void TdsExecuteRPC_OnFailure(Exception exc, TdsParserStateObject stateObj) {
8565 RuntimeHelpers.PrepareConstrainedRegions();
8566 try {
8567 #if DEBUG
8568 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
8570 RuntimeHelpers.PrepareConstrainedRegions();
8571 try {
8572 tdsReliabilitySection.Start();
8573 #else
8575 #endif //DEBUG
8576 FailureCleanup(stateObj, exc);
8578 #if DEBUG
8579 finally {
8580 tdsReliabilitySection.Stop();
8582 #endif //DEBUG
8584 catch (System.OutOfMemoryException) {
8585 _connHandler.DoomThisConnection();
8586 throw;
8588 catch (System.StackOverflowException) {
8589 _connHandler.DoomThisConnection();
8590 throw;
8592 catch (System.Threading.ThreadAbortException) {
8593 _connHandler.DoomThisConnection();
8594 throw;
8598 private void ExecuteFlushTaskCallback(Task tsk, TdsParserStateObject stateObj, TaskCompletionSource<object> completion, bool releaseConnectionLock) {
8599 try {
8600 FinalizeExecuteRPC(stateObj);
8601 if (tsk.Exception != null) {
8602 Exception exc = tsk.Exception.InnerException;
8603 RuntimeHelpers.PrepareConstrainedRegions();
8604 try {
8605 #if DEBUG
8606 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
8608 RuntimeHelpers.PrepareConstrainedRegions();
8609 try {
8610 tdsReliabilitySection.Start();
8611 #else
8613 #endif //DEBUG
8614 FailureCleanup(stateObj, tsk.Exception);
8616 #if DEBUG
8617 finally {
8618 tdsReliabilitySection.Stop();
8620 #endif //DEBUG
8622 catch (System.OutOfMemoryException e) {
8623 _connHandler.DoomThisConnection();
8624 completion.SetException(e);
8625 throw;
8627 catch (System.StackOverflowException e) {
8628 _connHandler.DoomThisConnection();
8629 completion.SetException(e);
8630 throw;
8632 catch (System.Threading.ThreadAbortException e) {
8633 _connHandler.DoomThisConnection();
8634 completion.SetException(e);
8635 throw;
8637 catch (Exception e) {
8638 exc = e;
8640 completion.SetException(exc);
8642 else {
8643 completion.SetResult(null);
8646 finally {
8647 if (releaseConnectionLock) {
8648 _connHandler._parserLock.Release();
8654 private void WriteParameterName(string parameterName, TdsParserStateObject stateObj) {
8655 // paramLen
8656 // paramName
8657 if (!ADP.IsEmpty(parameterName)) {
8658 Debug.Assert(parameterName.Length <= 0xff, "parameter name can only be 255 bytes, shouldn't get to TdsParser!");
8659 int tempLen = parameterName.Length & 0xff;
8660 stateObj.WriteByte((byte)tempLen);
8661 WriteString(parameterName, tempLen, 0, stateObj);
8663 else {
8664 stateObj.WriteByte(0);
8668 private static readonly IEnumerable<MSS.SqlDataRecord> __tvpEmptyValue = new List<MSS.SqlDataRecord>().AsReadOnly();
8669 private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefault, TdsParserStateObject stateObj) {
8671 // Determine Metadata
8673 ParameterPeekAheadValue peekAhead;
8674 MSS.SmiParameterMetaData metaData = param.MetaDataForSmi(out peekAhead);
8676 if (!_isKatmai) {
8677 MetaType mt = MetaType.GetMetaTypeFromSqlDbType(metaData.SqlDbType, metaData.IsMultiValued);
8678 throw ADP.VersionDoesNotSupportDataType(mt.TypeName);
8682 // Determine value to send
8684 object value;
8685 MSS.ExtendedClrTypeCode typeCode;
8687 // if we have an output or default param, set the value to null so we do not send it across to the server
8688 if (sendDefault) {
8689 // Value for TVP default is empty list, not NULL
8690 if (SqlDbType.Structured == metaData.SqlDbType && metaData.IsMultiValued) {
8691 value = __tvpEmptyValue;
8692 typeCode = MSS.ExtendedClrTypeCode.IEnumerableOfSqlDataRecord;
8694 else {
8695 // Need to send null value for default
8696 value = null;
8697 typeCode = MSS.ExtendedClrTypeCode.DBNull;
8700 else if (param.Direction == ParameterDirection.Output) {
8701 bool isCLRType = param.ParamaterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the paramater we will no longer be able to distinguish what type were seeing.
8702 param.Value = null;
8703 value = null;
8704 typeCode = MSS.ExtendedClrTypeCode.DBNull;
8705 param.ParamaterIsSqlType = isCLRType;
8707 else {
8708 value = param.GetCoercedValue();
8709 typeCode = MSS.MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType(
8710 metaData.SqlDbType, metaData.IsMultiValued, value, null, MSS.SmiContextFactory.KatmaiVersion);
8713 if (Bid.AdvancedOn) {
8714 Bid.Trace("<sc.TdsParser.WriteSmiParameter|ADV> %d#, Sending parameter '%ls', default flag=%d, metadata:\n", ObjectID, param.ParameterName, sendDefault?1:0);
8715 Bid.PutStr(metaData.TraceString(3));
8716 Bid.Trace("\n");
8720 // Write parameter metadata
8722 WriteSmiParameterMetaData(metaData, sendDefault, stateObj);
8725 // Now write the value
8727 TdsParameterSetter paramSetter = new TdsParameterSetter(stateObj, metaData);
8728 MSS.ValueUtilsSmi.SetCompatibleValueV200(
8729 new MSS.SmiEventSink_Default(), // TDS Errors/events dealt with at lower level for now, just need an object for processing
8730 paramSetter,
8731 0, // ordinal. TdsParameterSetter only handles one parameter at a time
8732 metaData,
8733 value,
8734 typeCode,
8735 param.Offset,
8736 0 < param.Size ? param.Size : -1,
8737 peekAhead);
8740 // Writes metadata portion of parameter stream from an SmiParameterMetaData object.
8741 private void WriteSmiParameterMetaData(MSS.SmiParameterMetaData metaData, bool sendDefault, TdsParserStateObject stateObj) {
8742 // Determine status
8743 byte status = 0;
8744 if (ParameterDirection.Output == metaData.Direction || ParameterDirection.InputOutput == metaData.Direction) {
8745 status |= TdsEnums.RPC_PARAM_BYREF;
8748 if (sendDefault) {
8749 status |= TdsEnums.RPC_PARAM_DEFAULT;
8752 // Write everything out
8753 WriteParameterName(metaData.Name, stateObj);
8754 stateObj.WriteByte(status);
8755 WriteSmiTypeInfo(metaData, stateObj);
8758 // Write a TypeInfo stream
8759 // Devnote: we remap the legacy types (text, ntext, and image) to SQLBIGVARCHAR, SQLNVARCHAR, and SQLBIGVARBINARY
8760 private void WriteSmiTypeInfo(MSS.SmiExtendedMetaData metaData, TdsParserStateObject stateObj) {
8761 switch(metaData.SqlDbType) {
8762 case SqlDbType.BigInt:
8763 stateObj.WriteByte(TdsEnums.SQLINTN);
8764 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8765 break;
8766 case SqlDbType.Binary:
8767 stateObj.WriteByte(TdsEnums.SQLBIGBINARY);
8768 WriteUnsignedShort(checked((ushort)metaData.MaxLength), stateObj);
8769 break;
8770 case SqlDbType.Bit:
8771 stateObj.WriteByte(TdsEnums.SQLBITN);
8772 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8773 break;
8774 case SqlDbType.Char:
8775 stateObj.WriteByte(TdsEnums.SQLBIGCHAR);
8776 WriteUnsignedShort(checked((ushort)(metaData.MaxLength)), stateObj);
8777 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8778 stateObj.WriteByte(_defaultCollation.sortId);
8779 break;
8780 case SqlDbType.DateTime:
8781 stateObj.WriteByte(TdsEnums.SQLDATETIMN);
8782 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8783 break;
8784 case SqlDbType.Decimal:
8785 stateObj.WriteByte(TdsEnums.SQLNUMERICN);
8786 stateObj.WriteByte(checked((byte)MetaType.MetaDecimal.FixedLength)); // SmiMetaData's length and actual wire format's length are different
8787 stateObj.WriteByte(0 == metaData.Precision ? (byte)1 : metaData.Precision);
8788 stateObj.WriteByte(metaData.Scale);
8789 break;
8790 case SqlDbType.Float:
8791 stateObj.WriteByte(TdsEnums.SQLFLTN);
8792 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8793 break;
8794 case SqlDbType.Image:
8795 stateObj.WriteByte(TdsEnums.SQLBIGVARBINARY);
8796 WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj);
8797 break;
8798 case SqlDbType.Int:
8799 stateObj.WriteByte(TdsEnums.SQLINTN);
8800 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8801 break;
8802 case SqlDbType.Money:
8803 stateObj.WriteByte(TdsEnums.SQLMONEYN);
8804 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8805 break;
8806 case SqlDbType.NChar:
8807 stateObj.WriteByte(TdsEnums.SQLNCHAR);
8808 WriteUnsignedShort(checked((ushort)(metaData.MaxLength*2)), stateObj);
8809 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8810 stateObj.WriteByte(_defaultCollation.sortId);
8811 break;
8812 case SqlDbType.NText:
8813 stateObj.WriteByte(TdsEnums.SQLNVARCHAR);
8814 WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj);
8815 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8816 stateObj.WriteByte(_defaultCollation.sortId);
8817 break;
8818 case SqlDbType.NVarChar:
8819 stateObj.WriteByte(TdsEnums.SQLNVARCHAR);
8820 if (MSS.SmiMetaData.UnlimitedMaxLengthIndicator == metaData.MaxLength) {
8821 WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj);
8823 else {
8824 WriteUnsignedShort(checked((ushort)(metaData.MaxLength*2)), stateObj);
8826 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8827 stateObj.WriteByte(_defaultCollation.sortId);
8828 break;
8829 case SqlDbType.Real:
8830 stateObj.WriteByte(TdsEnums.SQLFLTN);
8831 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8832 break;
8833 case SqlDbType.UniqueIdentifier:
8834 stateObj.WriteByte(TdsEnums.SQLUNIQUEID);
8835 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8836 break;
8837 case SqlDbType.SmallDateTime:
8838 stateObj.WriteByte(TdsEnums.SQLDATETIMN);
8839 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8840 break;
8841 case SqlDbType.SmallInt:
8842 stateObj.WriteByte(TdsEnums.SQLINTN);
8843 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8844 break;
8845 case SqlDbType.SmallMoney:
8846 stateObj.WriteByte(TdsEnums.SQLMONEYN);
8847 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8848 break;
8849 case SqlDbType.Text:
8850 stateObj.WriteByte(TdsEnums.SQLBIGVARCHAR);
8851 WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj);
8852 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8853 stateObj.WriteByte(_defaultCollation.sortId);
8854 break;
8855 case SqlDbType.Timestamp:
8856 stateObj.WriteByte(TdsEnums.SQLBIGBINARY);
8857 WriteShort(checked((int)metaData.MaxLength), stateObj);
8858 break;
8859 case SqlDbType.TinyInt:
8860 stateObj.WriteByte(TdsEnums.SQLINTN);
8861 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8862 break;
8863 case SqlDbType.VarBinary:
8864 stateObj.WriteByte(TdsEnums.SQLBIGVARBINARY);
8865 WriteUnsignedShort(unchecked((ushort)metaData.MaxLength), stateObj);
8866 break;
8867 case SqlDbType.VarChar:
8868 stateObj.WriteByte(TdsEnums.SQLBIGVARCHAR);
8869 WriteUnsignedShort(unchecked((ushort)metaData.MaxLength), stateObj);
8870 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8871 stateObj.WriteByte(_defaultCollation.sortId);
8872 break;
8873 case SqlDbType.Variant:
8874 stateObj.WriteByte(TdsEnums.SQLVARIANT);
8875 WriteInt(checked((int)metaData.MaxLength), stateObj);
8876 break;
8877 case SqlDbType.Xml:
8878 stateObj.WriteByte(TdsEnums.SQLXMLTYPE);
8879 // Is there a schema
8880 if (ADP.IsEmpty(metaData.TypeSpecificNamePart1) && ADP.IsEmpty(metaData.TypeSpecificNamePart2) &&
8881 ADP.IsEmpty(metaData.TypeSpecificNamePart3)) {
8882 stateObj.WriteByte(0); // schema not present
8884 else {
8885 stateObj.WriteByte(1); // schema present
8886 WriteIdentifier(metaData.TypeSpecificNamePart1, stateObj);
8887 WriteIdentifier(metaData.TypeSpecificNamePart2, stateObj);
8888 WriteIdentifierWithShortLength(metaData.TypeSpecificNamePart3, stateObj);
8890 break;
8891 case SqlDbType.Udt:
8892 stateObj.WriteByte(TdsEnums.SQLUDT);
8893 WriteIdentifier(metaData.TypeSpecificNamePart1, stateObj);
8894 WriteIdentifier(metaData.TypeSpecificNamePart2, stateObj);
8895 WriteIdentifier(metaData.TypeSpecificNamePart3, stateObj);
8896 break;
8897 case SqlDbType.Structured:
8898 if (metaData.IsMultiValued) {
8899 WriteTvpTypeInfo(metaData, stateObj);
8901 else {
8902 Debug.Assert(false, "SUDTs not yet supported.");
8904 break;
8905 case SqlDbType.Date:
8906 stateObj.WriteByte(TdsEnums.SQLDATE);
8907 break;
8908 case SqlDbType.Time:
8909 stateObj.WriteByte(TdsEnums.SQLTIME);
8910 stateObj.WriteByte(metaData.Scale);
8911 break;
8912 case SqlDbType.DateTime2:
8913 stateObj.WriteByte(TdsEnums.SQLDATETIME2);
8914 stateObj.WriteByte(metaData.Scale);
8915 break;
8916 case SqlDbType.DateTimeOffset:
8917 stateObj.WriteByte(TdsEnums.SQLDATETIMEOFFSET);
8918 stateObj.WriteByte(metaData.Scale);
8919 break;
8920 default:
8921 Debug.Assert(false, "Unknown SqlDbType should have been caught earlier!");
8922 break;
8926 private void WriteTvpTypeInfo(MSS.SmiExtendedMetaData metaData, TdsParserStateObject stateObj) {
8927 Debug.Assert(SqlDbType.Structured == metaData.SqlDbType && metaData.IsMultiValued,
8928 "Invalid metadata for TVPs. Type=" + metaData.SqlDbType);
8929 // Type token
8930 stateObj.WriteByte((byte)TdsEnums.SQLTABLE);
8932 // 3-part name (DB, Schema, TypeName)
8933 WriteIdentifier(metaData.TypeSpecificNamePart1, stateObj);
8934 WriteIdentifier(metaData.TypeSpecificNamePart2, stateObj);
8935 WriteIdentifier(metaData.TypeSpecificNamePart3, stateObj);
8937 // TVP_COLMETADATA
8938 if (0 == metaData.FieldMetaData.Count) {
8939 WriteUnsignedShort((ushort)TdsEnums.TVP_NOMETADATA_TOKEN, stateObj);
8941 else {
8942 // COUNT of columns
8943 WriteUnsignedShort(checked((ushort) metaData.FieldMetaData.Count), stateObj);
8945 // TvpColumnMetaData for each column (look for defaults in this loop
8946 MSS.SmiDefaultFieldsProperty defaults = (MSS.SmiDefaultFieldsProperty) metaData.ExtendedProperties[MSS.SmiPropertySelector.DefaultFields];
8947 for(int i=0; i<metaData.FieldMetaData.Count; i++) {
8948 WriteTvpColumnMetaData(metaData.FieldMetaData[i], defaults[i], stateObj);
8951 // optional OrderUnique metadata
8952 WriteTvpOrderUnique(metaData, stateObj);
8956 // END of optional metadata
8957 stateObj.WriteByte(TdsEnums.TVP_END_TOKEN);
8960 // Write a single TvpColumnMetaData stream to the server
8961 private void WriteTvpColumnMetaData(MSS.SmiExtendedMetaData md, bool isDefault, TdsParserStateObject stateObj) {
8962 // User Type
8963 if (SqlDbType.Timestamp == md.SqlDbType) {
8964 WriteUnsignedInt(TdsEnums.SQLTIMESTAMP, stateObj);
8965 } else {
8966 WriteUnsignedInt(0, stateObj);
8969 // Flags
8970 ushort status = TdsEnums.Nullable;
8971 if (isDefault) {
8972 status |= TdsEnums.TVP_DEFAULT_COLUMN;
8974 WriteUnsignedShort(status, stateObj);
8976 // Type info
8977 WriteSmiTypeInfo(md, stateObj);
8979 // Column name
8980 // per spec, "ColName is never sent to server or client for TVP, it is required within a TVP to be zero length."
8981 WriteIdentifier(null, stateObj);
8984 // temporary-results structure used only by WriteTvpOrderUnique
8985 // use class to avoid List<T>'s per-struct-instantiated memory costs.
8986 private class TdsOrderUnique {
8987 internal short ColumnOrdinal;
8988 internal byte Flags;
8990 internal TdsOrderUnique(short ordinal, byte flags) {
8991 ColumnOrdinal = ordinal;
8992 Flags = flags;
8996 private void WriteTvpOrderUnique(MSS.SmiExtendedMetaData metaData, TdsParserStateObject stateObj) {
8997 // TVP_ORDER_UNIQUE token (uniqueness and sort order)
8999 // Merge order and unique keys into a single token stream
9001 MSS.SmiOrderProperty orderProperty = (MSS.SmiOrderProperty) metaData.ExtendedProperties[MSS.SmiPropertySelector.SortOrder];
9002 MSS.SmiUniqueKeyProperty uniqueKeyProperty = (MSS.SmiUniqueKeyProperty) metaData.ExtendedProperties[MSS.SmiPropertySelector.UniqueKey];
9004 // Build list from
9005 List<TdsOrderUnique> columnList = new List<TdsOrderUnique>(metaData.FieldMetaData.Count);
9006 for(int i=0; i<metaData.FieldMetaData.Count; i++) {
9008 // Add appropriate SortOrder flag
9009 byte flags = 0;
9010 MSS.SmiOrderProperty.SmiColumnOrder columnOrder = orderProperty[i];
9011 if (SortOrder.Ascending == columnOrder.Order) {
9012 flags = TdsEnums.TVP_ORDERASC_FLAG;
9014 else if (SortOrder.Descending == columnOrder.Order) {
9015 flags = TdsEnums.TVP_ORDERDESC_FLAG;
9018 // Add unique key flage if appropriate
9019 if (uniqueKeyProperty[i]) {
9020 flags |= TdsEnums.TVP_UNIQUE_FLAG;
9023 // Remember this column if any flags were set
9024 if (0 != flags) {
9025 columnList.Add(new TdsOrderUnique(checked((short)(i+1)), flags));
9029 // Write flagged columns to wire...
9030 if (0 < columnList.Count) {
9031 stateObj.WriteByte(TdsEnums.TVP_ORDER_UNIQUE_TOKEN);
9032 WriteShort(columnList.Count, stateObj);
9033 foreach(TdsOrderUnique column in columnList) {
9034 WriteShort(column.ColumnOrdinal, stateObj);
9035 stateObj.WriteByte(column.Flags);
9040 internal Task WriteBulkCopyDone(TdsParserStateObject stateObj) {
9041 // Write DONE packet
9043 if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) {
9044 throw ADP.ClosedConnectionError();
9046 stateObj.WriteByte(TdsEnums.SQLDONE);
9047 WriteShort(0, stateObj);
9048 WriteShort(0, stateObj);
9049 WriteInt(0, stateObj);
9051 stateObj._pendingData = true;
9052 stateObj._messageStatus = 0;
9053 return stateObj.WritePacket(TdsEnums.HARDFLUSH);
9056 /// <summary>
9057 /// Loads the column encryptions keys into cache. This will read the master key info,
9058 /// decrypt the CEK and keep it ready for encryption.
9059 /// </summary>
9060 /// <returns></returns>
9061 internal void LoadColumnEncryptionKeys (_SqlMetaDataSet metadataCollection, string serverName) {
9062 if (_serverSupportsColumnEncryption && ShouldEncryptValuesForBulkCopy()) {
9063 for (int col = 0; col < metadataCollection.Length; col++) {
9064 if (null != metadataCollection[col]) {
9065 _SqlMetaData md = metadataCollection[col];
9066 if (md.isEncrypted) {
9067 SqlSecurityUtility.DecryptSymmetricKey(md.cipherMD, serverName);
9074 /// <summary>
9075 /// Writes a single entry of CEK Table into TDS Stream (for bulk copy).
9076 /// </summary>
9077 /// <returns></returns>
9078 internal void WriteEncryptionEntries (ref SqlTceCipherInfoTable cekTable, TdsParserStateObject stateObj) {
9079 for (int i =0; i < cekTable.Size; i++) {
9080 // Write Db ID
9081 WriteInt(cekTable[i].DatabaseId, stateObj);
9083 // Write Key ID
9084 WriteInt(cekTable[i].CekId, stateObj);
9086 // Write Key Version
9087 WriteInt(cekTable[i].CekVersion, stateObj);
9089 // Write 8 bytes of key MD Version
9090 Debug.Assert (8 == cekTable[i].CekMdVersion.Length);
9091 stateObj.WriteByteArray (cekTable[i].CekMdVersion, 8, 0);
9093 // We don't really need to send the keys
9094 stateObj.WriteByte(0x00);
9098 /// <summary>
9099 /// Writes a CEK Table (as part of COLMETADATA token) for bulk copy.
9100 /// </summary>
9101 /// <returns></returns>
9102 internal void WriteCekTable (_SqlMetaDataSet metadataCollection, TdsParserStateObject stateObj) {
9103 if (!_serverSupportsColumnEncryption) {
9104 return;
9107 // If no cek table is present, send a count of 0 for table size
9108 // Note- Cek table (with 0 entries) will be present if TCE
9109 // was enabled and server supports it!
9110 // OR if encryption was disabled in connection options
9111 if (!metadataCollection.cekTable.HasValue ||
9112 !ShouldEncryptValuesForBulkCopy()) {
9113 WriteShort(0x00, stateObj);
9114 return;
9117 SqlTceCipherInfoTable cekTable = metadataCollection.cekTable.Value;
9118 ushort count = (ushort)cekTable.Size;
9120 WriteShort(count, stateObj);
9122 WriteEncryptionEntries(ref cekTable, stateObj);
9125 /// <summary>
9126 /// Writes the UserType and TYPE_INFO values for CryptoMetadata (for bulk copy).
9127 /// </summary>
9128 /// <returns></returns>
9129 internal void WriteTceUserTypeAndTypeInfo(SqlMetaDataPriv mdPriv, TdsParserStateObject stateObj) {
9130 // Write the UserType (4 byte value)
9131 WriteInt(0x0, stateObj); //
9133 Debug.Assert(SqlDbType.Xml != mdPriv.type);
9134 Debug.Assert(SqlDbType.Udt != mdPriv.type);
9136 stateObj.WriteByte(mdPriv.tdsType);
9138 switch (mdPriv.type) {
9139 case SqlDbType.Decimal:
9140 WriteTokenLength(mdPriv.tdsType, mdPriv.length, stateObj);
9141 stateObj.WriteByte(mdPriv.precision);
9142 stateObj.WriteByte(mdPriv.scale);
9143 break;
9144 case SqlDbType.Date:
9145 // Nothing more to write!
9146 break;
9147 case SqlDbType.Time:
9148 case SqlDbType.DateTime2:
9149 case SqlDbType.DateTimeOffset:
9150 stateObj.WriteByte(mdPriv.scale);
9151 break;
9152 default:
9153 WriteTokenLength(mdPriv.tdsType, mdPriv.length, stateObj);
9154 if (mdPriv.metaType.IsCharType && _isShiloh) {
9155 WriteUnsignedInt(mdPriv.collation.info, stateObj);
9156 stateObj.WriteByte(mdPriv.collation.sortId);
9158 break;
9162 /// <summary>
9163 /// Writes the crypto metadata (as part of COLMETADATA token) for encrypted columns.
9164 /// </summary>
9165 /// <returns></returns>
9166 internal void WriteCryptoMetadata(_SqlMetaData md, TdsParserStateObject stateObj) {
9167 if (!_serverSupportsColumnEncryption || // TCE Feature supported
9168 !md.isEncrypted || // Column is not encrypted
9169 !ShouldEncryptValuesForBulkCopy()) { // TCE disabled on connection string
9170 return;
9173 // Write the ordinal
9174 WriteShort (md.cipherMD.CekTableOrdinal, stateObj);
9176 // Write UserType and TYPEINFO
9177 WriteTceUserTypeAndTypeInfo(md.baseTI, stateObj);
9179 // Write Encryption Algo
9180 stateObj.WriteByte(md.cipherMD.CipherAlgorithmId);
9182 if (TdsEnums.CustomCipherAlgorithmId == md.cipherMD.CipherAlgorithmId) {
9183 // Write the algorithm name
9184 Debug.Assert (md.cipherMD.CipherAlgorithmName.Length < 256);
9185 stateObj.WriteByte((byte)md.cipherMD.CipherAlgorithmName.Length);
9186 WriteString(md.cipherMD.CipherAlgorithmName, stateObj);
9189 // Write Encryption Algo Type
9190 stateObj.WriteByte(md.cipherMD.EncryptionType);
9192 // Write Normalization Version
9193 stateObj.WriteByte(md.cipherMD.NormalizationRuleVersion);
9196 internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int count, TdsParserStateObject stateObj) {
9197 if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) {
9198 throw ADP.ClosedConnectionError();
9201 stateObj.WriteByte(TdsEnums.SQLCOLMETADATA);
9202 WriteShort(count, stateObj);
9204 // Write CEK table - 0 count
9205 WriteCekTable(metadataCollection, stateObj);
9207 for (int i = 0; i < metadataCollection.Length; i++) {
9208 if (metadataCollection[i] != null) {
9209 _SqlMetaData md = metadataCollection[i];
9211 // read user type - 4 bytes Yukon, 2 backwards
9212 if (IsYukonOrNewer) {
9213 WriteInt(0x0, stateObj);
9215 else {
9216 WriteShort(0x0000, stateObj);
9219 // Write the flags
9220 UInt16 flags;
9221 flags = (UInt16)(md.updatability << 2);
9222 flags |= (UInt16)(md.isNullable ? (UInt16)TdsEnums.Nullable : (UInt16)0);
9223 flags |= (UInt16)(md.isIdentity ? (UInt16)TdsEnums.Identity : (UInt16)0);
9225 // Write the next byte of flags
9226 if (_serverSupportsColumnEncryption) { // TCE Supported
9227 if (ShouldEncryptValuesForBulkCopy()) { // TCE enabled on connection options
9228 flags |= (UInt16)(md.isEncrypted ? (UInt16)(TdsEnums.IsEncrypted << 8) : (UInt16)0);
9232 WriteShort(flags, stateObj);// write the flags
9234 // todo:
9235 // for xml WriteTokenLength results in a no-op
9236 // discuss this with blaine ...
9237 // (Microsoft) xml datatype does not have token length in its metadata. So it should be a noop.
9239 switch (md.type) {
9240 case SqlDbType.Decimal:
9241 stateObj.WriteByte(md.tdsType);
9242 WriteTokenLength(md.tdsType, md.length, stateObj);
9243 stateObj.WriteByte(md.precision);
9244 stateObj.WriteByte(md.scale);
9245 break;
9246 case SqlDbType.Xml:
9248 stateObj.WriteByteArray(s_xmlMetadataSubstituteSequence, s_xmlMetadataSubstituteSequence.Length, 0);
9249 break;
9250 case SqlDbType.Udt:
9251 stateObj.WriteByte(TdsEnums.SQLBIGVARBINARY);
9252 WriteTokenLength(TdsEnums.SQLBIGVARBINARY, md.length, stateObj);
9253 break;
9254 case SqlDbType.Date:
9255 stateObj.WriteByte(md.tdsType);
9256 break;
9257 case SqlDbType.Time:
9258 case SqlDbType.DateTime2:
9259 case SqlDbType.DateTimeOffset:
9260 stateObj.WriteByte(md.tdsType);
9261 stateObj.WriteByte(md.scale);
9262 break;
9263 default:
9264 stateObj.WriteByte(md.tdsType);
9265 WriteTokenLength(md.tdsType, md.length, stateObj);
9266 if (md.metaType.IsCharType && _isShiloh) {
9267 WriteUnsignedInt(md.collation.info, stateObj);
9268 stateObj.WriteByte(md.collation.sortId);
9270 break;
9273 if (md.metaType.IsLong && !md.metaType.IsPlp) {
9274 WriteShort(md.tableName.Length, stateObj);
9275 WriteString(md.tableName, stateObj);
9278 WriteCryptoMetadata(md, stateObj);
9280 stateObj.WriteByte((byte)md.column.Length);
9281 WriteString(md.column, stateObj);
9283 } // end for loop
9286 /// <summary>
9287 /// Determines if a column value should be encrypted when using BulkCopy (based on connectionstring setting).
9288 /// </summary>
9289 /// <returns></returns>
9290 internal bool ShouldEncryptValuesForBulkCopy () {
9291 if (null != _connHandler &&
9292 null != _connHandler.ConnectionOptions &&
9293 SqlConnectionColumnEncryptionSetting.Enabled == _connHandler.ConnectionOptions.ColumnEncryptionSetting) {
9294 return true;
9297 return false;
9300 /// <summary>
9301 /// Encrypts a column value (for SqlBulkCopy)
9302 /// </summary>
9303 /// <returns></returns>
9304 internal object EncryptColumnValue (object value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) {
9305 Debug.Assert (_serverSupportsColumnEncryption, "Server doesn't support encryption, yet we received encryption metadata");
9306 Debug.Assert (ShouldEncryptValuesForBulkCopy(), "Encryption attempted when not requested");
9308 if (isDataFeed) { // can't encrypt a stream column
9309 SQL.StreamNotSupportOnEncryptedColumn(column);
9312 int actualLengthInBytes;
9313 switch(metadata.baseTI.metaType.NullableType) {
9314 case TdsEnums.SQLBIGBINARY:
9315 case TdsEnums.SQLBIGVARBINARY:
9316 case TdsEnums.SQLIMAGE:
9317 // For some datatypes, engine does truncation before storing the value. (For example, when
9318 // trying to insert a varbinary(7000) into a varbinary(3000) column). Since we encrypt the
9319 // column values, engine has no way to tell the size of the plaintext datatype. Therefore,
9320 // we truncate the values based on target column sizes here before encrypting them. This
9321 // truncation is only needed if we exceed the max column length or if the target column is
9322 // not a blob type (eg. varbinary(max)). The actual work of truncating the column happens
9323 // when we normalize and serialize the data buffers. The serialization routine expects us
9324 // to report the size of data to be copied out (for serialization). If we underreport the
9325 // size, truncation will happen for us!
9326 actualLengthInBytes = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length;
9327 if (metadata.baseTI.length > 0 &&
9328 actualLengthInBytes > metadata.baseTI.length) { // see comments agove
9329 actualLengthInBytes = metadata.baseTI.length;
9331 break;
9333 case TdsEnums.SQLUNIQUEID:
9334 actualLengthInBytes = GUID_SIZE; // that's a constant for guid
9335 break;
9336 case TdsEnums.SQLBIGCHAR:
9337 case TdsEnums.SQLBIGVARCHAR:
9338 case TdsEnums.SQLTEXT:
9339 if (null == _defaultEncoding)
9341 ThrowUnsupportedCollationEncountered(null); // stateObject only when reading
9344 string stringValue = (isSqlType) ? ((SqlString)value).Value : (string)value;
9345 actualLengthInBytes = _defaultEncoding.GetByteCount(stringValue);
9347 // If the string length is > max length, then use the max length (see comments above)
9348 if (metadata.baseTI.length > 0 &&
9349 actualLengthInBytes > metadata.baseTI.length) {
9350 actualLengthInBytes = metadata.baseTI.length; // this ensure truncation!
9353 break;
9354 case TdsEnums.SQLNCHAR:
9355 case TdsEnums.SQLNVARCHAR:
9356 case TdsEnums.SQLNTEXT:
9357 actualLengthInBytes = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2;
9359 if (metadata.baseTI.length > 0 &&
9360 actualLengthInBytes > metadata.baseTI.length) { // see comments above
9361 actualLengthInBytes = metadata.baseTI.length;
9364 break;
9366 default:
9367 actualLengthInBytes = metadata.baseTI.length;
9368 break;
9371 byte[] serializedValue;
9372 if (isSqlType) {
9373 // SqlType
9374 serializedValue = SerializeUnencryptedSqlValue (value,
9375 metadata.baseTI.metaType,
9376 actualLengthInBytes,
9377 offset : 0,
9378 normalizationVersion: metadata.cipherMD.NormalizationRuleVersion,
9379 stateObj: stateObj);
9381 else {
9382 serializedValue = SerializeUnencryptedValue (value,
9383 metadata.baseTI.metaType,
9384 metadata.baseTI.scale,
9385 actualLengthInBytes,
9386 offset: 0,
9387 isDataFeed: isDataFeed,
9388 normalizationVersion: metadata.cipherMD.NormalizationRuleVersion,
9389 stateObj: stateObj);
9392 Debug.Assert(serializedValue != null, "serializedValue should not be null in TdsExecuteRPC.");
9393 return SqlSecurityUtility.EncryptWithKey(
9394 serializedValue,
9395 metadata.cipherMD,
9396 _connHandler.ConnectionOptions.DataSource);
9399 internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) {
9400 Debug.Assert(!isSqlType || value is INullable, "isSqlType is true, but value can not be type cast to an INullable");
9401 Debug.Assert(!isDataFeed ^ value is DataFeed, "Incorrect value for isDataFeed");
9403 Encoding saveEncoding = _defaultEncoding;
9404 SqlCollation saveCollation = _defaultCollation;
9405 int saveCodePage = _defaultCodePage;
9406 int saveLCID = _defaultLCID;
9407 Task resultTask = null;
9408 Task internalWriteTask = null;
9410 if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) {
9411 throw ADP.ClosedConnectionError();
9413 try {
9414 if (metadata.encoding != null) {
9415 _defaultEncoding = metadata.encoding;
9417 if (metadata.collation != null) {
9418 _defaultCollation = metadata.collation;
9419 _defaultLCID = _defaultCollation.LCID;
9421 _defaultCodePage = metadata.codePage;
9423 MetaType metatype = metadata.metaType;
9424 int ccb = 0;
9425 int ccbStringBytes = 0;
9427 if (isNull) {
9428 // For UDT, remember we treat as binary even though it is a PLP
9429 if (metatype.IsPlp && (metatype.NullableType != TdsEnums.SQLUDT || metatype.IsLong)) {
9430 WriteLong(unchecked((long)TdsEnums.SQL_PLP_NULL), stateObj);
9432 else if (!metatype.IsFixed && !metatype.IsLong && !metatype.IsVarTime) {
9433 WriteShort(TdsEnums.VARNULL, stateObj);
9435 else {
9436 stateObj.WriteByte(TdsEnums.FIXEDNULL);
9438 return resultTask;
9441 if (!isDataFeed) {
9442 switch (metatype.NullableType) {
9443 case TdsEnums.SQLBIGBINARY:
9444 case TdsEnums.SQLBIGVARBINARY:
9445 case TdsEnums.SQLIMAGE:
9446 case TdsEnums.SQLUDT:
9447 ccb = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length;
9448 break;
9449 case TdsEnums.SQLUNIQUEID:
9450 ccb = GUID_SIZE; // that's a constant for guid
9451 break;
9452 case TdsEnums.SQLBIGCHAR:
9453 case TdsEnums.SQLBIGVARCHAR:
9454 case TdsEnums.SQLTEXT:
9455 if (null == _defaultEncoding) {
9456 ThrowUnsupportedCollationEncountered(null); // stateObject only when reading
9459 string stringValue = null;
9460 if (isSqlType) {
9461 stringValue = ((SqlString)value).Value;
9463 else {
9464 stringValue = (string)value;
9467 ccb = stringValue.Length;
9468 ccbStringBytes = _defaultEncoding.GetByteCount(stringValue);
9469 break;
9470 case TdsEnums.SQLNCHAR:
9471 case TdsEnums.SQLNVARCHAR:
9472 case TdsEnums.SQLNTEXT:
9473 ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2;
9474 break;
9475 case TdsEnums.SQLXMLTYPE:
9476 // Value here could be string or XmlReader
9477 if (value is XmlReader) {
9478 value = MetaType.GetStringFromXml((XmlReader)value);
9480 ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2;
9481 break;
9483 default:
9484 ccb = metadata.length;
9485 break;
9488 else {
9489 Debug.Assert(metatype.IsLong &&
9490 ((metatype.SqlDbType == SqlDbType.VarBinary && value is StreamDataFeed) ||
9491 ((metatype.SqlDbType == SqlDbType.VarChar || metatype.SqlDbType == SqlDbType.NVarChar) && value is TextDataFeed) ||
9492 (metatype.SqlDbType == SqlDbType.Xml && value is XmlDataFeed)),
9493 "Stream data feed should only be assigned to VarBinary(max), Text data feed should only be assigned to [N]VarChar(max), Xml data feed should only be assigned to XML(max)");
9497 // Expected the text length in data stream for bulk copy of text, ntext, or image data.
9499 if (metatype.IsLong) {
9500 switch (metatype.SqlDbType) {
9501 case SqlDbType.Text:
9502 case SqlDbType.NText:
9503 case SqlDbType.Image:
9504 stateObj.WriteByteArray(s_longDataHeader, s_longDataHeader.Length, 0);
9505 WriteTokenLength(metadata.tdsType, ccbStringBytes == 0 ? ccb : ccbStringBytes, stateObj);
9506 break;
9508 case SqlDbType.VarChar:
9509 case SqlDbType.NVarChar:
9510 case SqlDbType.VarBinary:
9511 case SqlDbType.Xml:
9512 case SqlDbType.Udt:
9513 // plp data
9514 WriteUnsignedLong(TdsEnums.SQL_PLP_UNKNOWNLEN, stateObj);
9515 break;
9518 else {
9519 WriteTokenLength(metadata.tdsType, ccbStringBytes == 0 ? ccb : ccbStringBytes, stateObj);
9522 if (isSqlType) {
9523 internalWriteTask = WriteSqlValue(value, metatype, ccb, ccbStringBytes, 0, stateObj);
9525 else if (metatype.SqlDbType != SqlDbType.Udt || metatype.IsLong) {
9526 internalWriteTask = WriteValue(value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed);
9527 if ((internalWriteTask == null) && (_asyncWrite)) {
9528 internalWriteTask = stateObj.WaitForAccumulatedWrites();
9530 Debug.Assert(_asyncWrite || stateObj.WaitForAccumulatedWrites() == null, "Should not have accumulated writes when writing sync");
9532 else {
9533 WriteShort(ccb, stateObj);
9534 internalWriteTask = stateObj.WriteByteArray((byte[])value, ccb, 0);
9537 #if DEBUG
9538 //In DEBUG mode, when SetAlwaysTaskOnWrite is true, we create a task. Allows us to verify async execution paths.
9539 if (_asyncWrite && internalWriteTask == null && SqlBulkCopy.SetAlwaysTaskOnWrite == true) {
9540 internalWriteTask = Task.FromResult<object>(null);
9542 #endif
9543 if (internalWriteTask != null) { //i.e. the write was async.
9544 resultTask = WriteBulkCopyValueSetupContinuation(internalWriteTask, saveEncoding, saveCollation, saveCodePage, saveLCID);
9547 finally {
9548 if (internalWriteTask == null) {
9549 _defaultEncoding = saveEncoding;
9550 _defaultCollation = saveCollation;
9551 _defaultCodePage = saveCodePage;
9552 _defaultLCID = saveLCID;
9555 return resultTask;
9558 // This is in its own method to avoid always allocating the lambda in WriteBulkCopyValue
9559 private Task WriteBulkCopyValueSetupContinuation(Task internalWriteTask, Encoding saveEncoding, SqlCollation saveCollation, int saveCodePage, int saveLCID) {
9560 return internalWriteTask.ContinueWith<Task>(t => {
9561 _defaultEncoding = saveEncoding;
9562 _defaultCollation = saveCollation;
9563 _defaultCodePage = saveCodePage;
9564 _defaultLCID = saveLCID;
9565 return t;
9566 }, TaskScheduler.Default).Unwrap();
9569 // Write mars header data, not including the mars header length
9570 private void WriteMarsHeaderData(TdsParserStateObject stateObj, SqlInternalTransaction transaction) {
9571 // Function to send over additional payload header data for Yukon and beyond only.
9572 Debug.Assert(_isYukon, "WriteMarsHeaderData called on a non-Yukon server");
9574 // These are not necessary - can have local started in distributed.
9575 // Debug.Assert(!(null != sqlTransaction && null != distributedTransaction), "Error to have local (api started) and distributed transaction at the same time!");
9576 // Debug.Assert(!(null != _userStartedLocalTransaction && null != distributedTransaction), "Error to have local (started outside of the api) and distributed transaction at the same time!");
9578 // We may need to update the mars header length if mars header is changed in the future
9580 WriteShort(TdsEnums.HEADERTYPE_MARS, stateObj);
9582 if (null != transaction && SqlInternalTransaction.NullTransactionId != transaction.TransactionId) {
9583 WriteLong(transaction.TransactionId, stateObj);
9584 WriteInt(stateObj.IncrementAndObtainOpenResultCount(transaction), stateObj);
9586 else {
9587 // If no transaction, send over retained transaction descriptor (empty if none retained)
9588 // and always 1 for result count.
9589 WriteLong(_retainedTransactionId, stateObj);
9590 WriteInt(stateObj.IncrementAndObtainOpenResultCount(null), stateObj);
9594 private int GetNotificationHeaderSize(SqlNotificationRequest notificationRequest) {
9595 if (null != notificationRequest) {
9596 string callbackId = notificationRequest.UserData;
9597 string service = notificationRequest.Options;
9598 int timeout = notificationRequest.Timeout;
9600 if (null == callbackId) {
9601 throw ADP.ArgumentNull("CallbackId");
9603 else if (UInt16.MaxValue < callbackId.Length) {
9604 throw ADP.ArgumentOutOfRange("CallbackId");
9607 if (null == service) {
9608 throw ADP.ArgumentNull("Service");
9610 else if (UInt16.MaxValue < service.Length) {
9611 throw ADP.ArgumentOutOfRange("Service");
9614 if (-1 > timeout) {
9615 throw ADP.ArgumentOutOfRange("Timeout");
9618 // Header Length (uint) (included in size) (already written to output buffer)
9619 // Header Type (ushort)
9620 // NotifyID Length (ushort)
9621 // NotifyID UnicodeStream (unicode text)
9622 // SSBDeployment Length (ushort)
9623 // SSBDeployment UnicodeStream (unicode text)
9624 // Timeout (uint) -- optional
9625 // WEBDATA 102263: Don't send timeout value if it is 0
9627 int headerLength = 4 + 2 + 2 + (callbackId.Length * 2) + 2 + (service.Length * 2);
9628 if (timeout > 0)
9629 headerLength += 4;
9630 return headerLength;
9632 else {
9633 return 0;
9637 // Write query notificaiton header data, not including the notificaiton header length
9638 private void WriteQueryNotificationHeaderData(SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj) {
9639 Debug.Assert(_isYukon, "WriteQueryNotificationHeaderData called on a non-Yukon server");
9641 // We may need to update the notification header length if the header is changed in the future
9643 Debug.Assert (null != notificationRequest, "notificaitonRequest is null");
9645 string callbackId = notificationRequest.UserData;
9646 string service = notificationRequest.Options;
9647 int timeout = notificationRequest.Timeout;
9649 // we did verification in GetNotificationHeaderSize, so just assert here.
9650 Debug.Assert(null != callbackId, "CallbackId is null");
9651 Debug.Assert(UInt16.MaxValue >= callbackId.Length, "CallbackId length is out of range");
9652 Debug.Assert(null != service, "Service is null");
9653 Debug.Assert(UInt16.MaxValue >= service.Length, "Service length is out of range");
9654 Debug.Assert(-1 <= timeout, "Timeout");
9657 Bid.NotificationsTrace("<sc.TdsParser.WriteQueryNotificationHeader|DEP> NotificationRequest: userData: '%ls', options: '%ls', timeout: '%d'\n", notificationRequest.UserData, notificationRequest.Options, notificationRequest.Timeout);
9659 WriteShort(TdsEnums.HEADERTYPE_QNOTIFICATION, stateObj); // Query notifications Type
9661 WriteShort(callbackId.Length * 2, stateObj); // Length in bytes
9662 WriteString(callbackId, stateObj);
9664 WriteShort(service.Length * 2, stateObj); // Length in bytes
9665 WriteString(service, stateObj);
9666 if (timeout > 0)
9667 WriteInt(timeout, stateObj);
9670 // Write the trace header data, not including the trace header length
9671 private void WriteTraceHeaderData(TdsParserStateObject stateObj) {
9672 Debug.Assert(this.IncludeTraceHeader, "WriteTraceHeaderData can only be called on a Denali or higher version server and bid trace with the control bit are on");
9674 // We may need to update the trace header length if trace header is changed in the future
9676 ActivityCorrelator.ActivityId actId = ActivityCorrelator.Current;
9678 WriteShort(TdsEnums.HEADERTYPE_TRACE, stateObj); // Trace Header Type
9680 stateObj.WriteByteArray(actId.Id.ToByteArray(), GUID_SIZE, 0); // Id (Guid)
9681 WriteUnsignedInt(actId.Sequence, stateObj); // sequence number
9683 Bid.Trace("<sc.TdsParser.WriteTraceHeaderData|INFO> ActivityID %ls\n", actId.ToString());
9686 private void WriteRPCBatchHeaders(TdsParserStateObject stateObj, SqlNotificationRequest notificationRequest) {
9687 Debug.Assert(_isYukon, "WriteRPCBatchHeaders can only be called on Yukon or higher version servers");
9689 /* Header:
9690 TotalLength - DWORD - including all headers and lengths, including itself
9691 Each Data Session:
9693 HeaderLength - DWORD - including all header length fields, including itself
9694 HeaderType - USHORT
9695 HeaderData
9699 int notificationHeaderSize = GetNotificationHeaderSize(notificationRequest);
9701 const int marsHeaderSize = 18; // 4 + 2 + 8 + 4
9703 // Header Length (DWORD)
9704 // Header Type (ushort)
9705 // Trace Data Guid
9706 // Trace Data Sequence Number (uint)
9707 const int traceHeaderSize = 26; // 4 + 2 + GUID_SIZE + sizeof(UInt32);
9709 // TotalLength - DWORD - including all headers and lengths, including itself
9710 int totalHeaderLength = this.IncludeTraceHeader ? (4 + marsHeaderSize + notificationHeaderSize + traceHeaderSize) : (4 + marsHeaderSize + notificationHeaderSize);
9711 Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length");
9712 // Write total header length
9713 WriteInt(totalHeaderLength, stateObj);
9715 // Write Mars header length
9716 WriteInt(marsHeaderSize, stateObj);
9717 // Write Mars header data
9718 WriteMarsHeaderData(stateObj, CurrentTransaction);
9720 if (0 != notificationHeaderSize) {
9721 // Write Notification header length
9722 WriteInt(notificationHeaderSize, stateObj);
9723 // Write notificaiton header data
9724 WriteQueryNotificationHeaderData(notificationRequest, stateObj);
9727 if (IncludeTraceHeader) {
9729 // Write trace header length
9730 WriteInt(traceHeaderSize, stateObj);
9731 // Write trace header data
9732 WriteTraceHeaderData(stateObj);
9738 // Reverse function of GetTokenLength
9740 private void WriteTokenLength(byte token, int length, TdsParserStateObject stateObj) {
9741 int tokenLength = 0;
9743 Debug.Assert(token != 0, "0 length token!");
9745 // For Plp fields, this should only be used when writing to metadata header.
9746 // For actual data length, WriteDataLength should be used.
9747 // For Xml fields, there is no token length field. For MAX fields it is 0xffff.
9748 if (_isYukon) { // Handle Yukon specific exceptions
9749 if (TdsEnums.SQLUDT == token) {
9750 tokenLength = 8;
9752 else if (token == TdsEnums.SQLXMLTYPE) {
9753 tokenLength = 8;
9757 if (tokenLength == 0) {
9758 switch (token & TdsEnums.SQLLenMask) {
9759 case TdsEnums.SQLFixedLen:
9760 Debug.Assert(length == 0x01 << ((token & 0x0c) >> 2), "length does not match encoded length in token");
9761 tokenLength = 0;
9762 break;
9764 case TdsEnums.SQLZeroLen:
9765 tokenLength = 0;
9766 break;
9768 case TdsEnums.SQLVarLen:
9769 case TdsEnums.SQLVarCnt:
9770 if (0 != (token & 0x80))
9771 tokenLength = 2;
9772 else if (0 == (token & 0x0c))
9775 tokenLength = 4;
9776 else
9777 tokenLength = 1;
9779 break;
9781 default:
9782 Debug.Assert(false, "Unknown token length!");
9783 break;
9786 switch (tokenLength) {
9787 case 1:
9788 stateObj.WriteByte((byte)length);
9789 break;
9791 case 2:
9792 WriteShort(length, stateObj);
9793 break;
9795 case 4:
9796 WriteInt(length, stateObj);
9797 break;
9799 case 8:
9800 // In the metadata case we write 0xffff for partial length prefixed types.
9801 // For actual data length preceding data, WriteDataLength should be used.
9802 WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj);
9803 break;
9804 } // end switch
9808 // Returns true if BOM byte mark is needed for an XML value
9809 private bool IsBOMNeeded(MetaType type, object value) {
9810 if (type.NullableType == TdsEnums.SQLXMLTYPE) {
9811 Type currentType = value.GetType();
9813 if(currentType == typeof(SqlString)) {
9814 if (!((SqlString)value).IsNull && ((((SqlString)value).Value).Length > 0)) {
9815 if ((((SqlString)value).Value[0] & 0xff) != 0xff)
9816 return true;
9819 else if ((currentType == typeof(String)) && (((String)value).Length > 0)) {
9820 if ((value != null) && (((String)value)[0] & 0xff) != 0xff)
9821 return true;
9823 else if (currentType == typeof(SqlXml)) {
9824 if (!((SqlXml)value).IsNull)
9825 return true;
9827 else if (currentType == typeof(XmlDataFeed)) {
9828 return true; // Values will eventually converted to unicode string here
9831 return false;
9834 private Task GetTerminationTask(Task unterminatedWriteTask, object value, MetaType type, int actualLength, TdsParserStateObject stateObj, bool isDataFeed) {
9835 if (type.IsPlp && ((actualLength > 0) || isDataFeed)) {
9836 if (unterminatedWriteTask == null) {
9837 WriteInt(0, stateObj);
9838 return null;
9840 else {
9841 return AsyncHelper.CreateContinuationTask<int, TdsParserStateObject>(unterminatedWriteTask,
9842 WriteInt, 0, stateObj,
9843 connectionToDoom: _connHandler);
9846 else {
9847 return unterminatedWriteTask;
9852 private Task WriteSqlValue(object value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) {
9853 return GetTerminationTask(
9854 WriteUnterminatedSqlValue(value, type, actualLength, codePageByteSize, offset, stateObj),
9855 value, type, actualLength, stateObj, false);
9858 // For MAX types, this method can only write everything in one big chunk. If multiple
9859 // chunk writes needed, please use WritePlpBytes/WritePlpChars
9860 private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) {
9861 Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) ||
9862 (value is INullable && !((INullable)value).IsNull)),
9863 "unexpected null SqlType!");
9865 // parameters are always sent over as BIG or N types
9866 switch (type.NullableType) {
9867 case TdsEnums.SQLFLTN:
9868 if (type.FixedLength == 4)
9869 WriteFloat(((SqlSingle)value).Value, stateObj);
9870 else {
9871 Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!");
9872 WriteDouble(((SqlDouble)value).Value, stateObj);
9875 break;
9877 case TdsEnums.SQLBIGBINARY:
9878 case TdsEnums.SQLBIGVARBINARY:
9879 case TdsEnums.SQLIMAGE:
9881 if (type.IsPlp) {
9882 WriteInt(actualLength, stateObj); // chunk length
9885 if (value is SqlBinary) {
9886 return stateObj.WriteByteArray(((SqlBinary)value).Value, actualLength, offset, canAccumulate:false);
9888 else {
9889 Debug.Assert(value is SqlBytes);
9890 return stateObj.WriteByteArray(((SqlBytes)value).Value, actualLength, offset, canAccumulate:false);
9894 case TdsEnums.SQLUNIQUEID:
9896 byte[] b = ((SqlGuid)value).ToByteArray();
9898 Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object");
9899 stateObj.WriteByteArray(b, actualLength, 0);
9900 break;
9903 case TdsEnums.SQLBITN:
9905 Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type");
9906 if (((SqlBoolean)value).Value == true)
9907 stateObj.WriteByte(1);
9908 else
9909 stateObj.WriteByte(0);
9911 break;
9914 case TdsEnums.SQLINTN:
9915 if (type.FixedLength == 1)
9916 stateObj.WriteByte(((SqlByte)value).Value);
9917 else
9918 if (type.FixedLength == 2)
9919 WriteShort(((SqlInt16)value).Value, stateObj);
9920 else
9921 if (type.FixedLength == 4)
9922 WriteInt(((SqlInt32)value).Value, stateObj);
9923 else {
9924 Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture));
9925 WriteLong(((SqlInt64)value).Value, stateObj);
9928 break;
9930 case TdsEnums.SQLBIGCHAR:
9931 case TdsEnums.SQLBIGVARCHAR:
9932 case TdsEnums.SQLTEXT:
9933 if (type.IsPlp) {
9934 WriteInt(codePageByteSize, stateObj); // chunk length
9936 if (value is SqlChars) {
9937 String sch = new String(((SqlChars)value).Value);
9939 return WriteEncodingChar(sch, actualLength, offset, _defaultEncoding, stateObj, canAccumulate:false);
9941 else {
9942 Debug.Assert(value is SqlString);
9943 return WriteEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate:false);
9947 case TdsEnums.SQLNCHAR:
9948 case TdsEnums.SQLNVARCHAR:
9949 case TdsEnums.SQLNTEXT:
9950 case TdsEnums.SQLXMLTYPE:
9952 if (type.IsPlp) {
9953 if(IsBOMNeeded(type, value)) {
9954 WriteInt(actualLength+2, stateObj); // chunk length
9955 WriteShort(TdsEnums.XMLUNICODEBOM , stateObj);
9956 } else {
9957 WriteInt(actualLength, stateObj); // chunk length
9961 // convert to cchars instead of cbytes
9962 // Xml type is already converted to string through GetCoercedValue
9963 if (actualLength != 0)
9964 actualLength >>= 1;
9966 if (value is SqlChars) {
9967 return WriteCharArray(((SqlChars)value).Value, actualLength, offset, stateObj, canAccumulate:false);
9969 else {
9970 Debug.Assert(value is SqlString);
9971 return WriteString(((SqlString)value).Value, actualLength, offset, stateObj, canAccumulate:false);
9974 case TdsEnums.SQLNUMERICN:
9975 Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes");
9976 WriteSqlDecimal((SqlDecimal)value, stateObj);
9977 break;
9979 case TdsEnums.SQLDATETIMN:
9980 SqlDateTime dt = (SqlDateTime)value;
9982 if (type.FixedLength == 4) {
9983 if (0 > dt.DayTicks || dt.DayTicks > UInt16.MaxValue)
9984 throw SQL.SmallDateTimeOverflow(dt.ToString());
9986 WriteShort(dt.DayTicks, stateObj);
9987 WriteShort(dt.TimeTicks / SqlDateTime.SQLTicksPerMinute, stateObj);
9989 else {
9990 WriteInt(dt.DayTicks, stateObj);
9991 WriteInt(dt.TimeTicks, stateObj);
9994 break;
9996 case TdsEnums.SQLMONEYN:
9998 WriteSqlMoney((SqlMoney)value, type.FixedLength, stateObj);
9999 break;
10002 case TdsEnums.SQLUDT:
10003 Debug.Assert(false, "Called WriteSqlValue on UDT param.Should have already been handled");
10004 throw SQL.UDTUnexpectedResult(value.GetType().AssemblyQualifiedName);
10006 default:
10007 Debug.Assert(false, "Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null));
10008 break;
10009 } // switch
10010 // return point for accumualated writes, note: non-accumulated writes returned from their case statements
10011 return null;
10014 private class TdsOutputStream : Stream {
10015 TdsParser _parser;
10016 TdsParserStateObject _stateObj;
10017 byte[] _preambleToStrip;
10019 public TdsOutputStream(TdsParser parser, TdsParserStateObject stateObj, byte[] preambleToStrip) {
10020 _parser = parser;
10021 _stateObj = stateObj;
10022 _preambleToStrip = preambleToStrip;
10025 public override bool CanRead {
10026 get { return false; }
10029 public override bool CanSeek {
10030 get { return false; }
10033 public override bool CanWrite {
10034 get { return true; }
10037 public override void Flush() {
10038 // NOOP
10041 public override long Length {
10042 get { throw new NotSupportedException(); }
10045 public override long Position {
10046 get {
10047 throw new NotSupportedException();
10049 set {
10050 throw new NotSupportedException();
10054 public override int Read(byte[] buffer, int offset, int count) {
10055 throw new NotSupportedException();
10058 public override long Seek(long offset, SeekOrigin origin) {
10059 throw new NotSupportedException();
10062 public override void SetLength(long value) {
10063 throw new NotSupportedException();
10066 private void StripPreamble(byte[] buffer, ref int offset, ref int count) {
10067 if (_preambleToStrip != null && count >= _preambleToStrip.Length) {
10069 for (int idx = 0; idx < _preambleToStrip.Length; idx++) {
10070 if (_preambleToStrip[idx] != buffer[idx]) {
10071 _preambleToStrip = null;
10072 return;
10076 offset += _preambleToStrip.Length;
10077 count -= _preambleToStrip.Length;
10079 _preambleToStrip = null;
10082 public override void Write(byte[] buffer, int offset, int count) {
10083 Debug.Assert(!_parser._asyncWrite);
10084 ValidateWriteParameters(buffer, offset, count);
10086 StripPreamble(buffer, ref offset, ref count);
10088 if (count > 0) {
10089 _parser.WriteInt(count, _stateObj); // write length of chunk
10090 _stateObj.WriteByteArray(buffer, count, offset);
10094 public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) {
10095 Debug.Assert(_parser._asyncWrite);
10096 ValidateWriteParameters(buffer, offset, count);
10098 StripPreamble(buffer, ref offset, ref count);
10100 RuntimeHelpers.PrepareConstrainedRegions();
10101 try {
10102 #if DEBUG
10103 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
10105 RuntimeHelpers.PrepareConstrainedRegions();
10106 try {
10107 tdsReliabilitySection.Start();
10108 #else
10110 #endif //DEBUG
10111 Task task = null;
10112 if (count > 0) {
10113 _parser.WriteInt(count, _stateObj); // write length of chunk
10114 task = _stateObj.WriteByteArray(buffer, count, offset, canAccumulate: false);
10116 if (task == null) {
10117 return CompletedTask;
10119 else {
10120 return task;
10123 #if DEBUG
10124 finally {
10125 tdsReliabilitySection.Stop();
10127 #endif //DEBUG
10129 catch (System.OutOfMemoryException) {
10130 _parser._connHandler.DoomThisConnection();
10131 throw;
10133 catch (System.StackOverflowException) {
10134 _parser._connHandler.DoomThisConnection();
10135 throw;
10137 catch (System.Threading.ThreadAbortException) {
10138 _parser._connHandler.DoomThisConnection();
10139 throw;
10143 internal static void ValidateWriteParameters(byte[] buffer, int offset, int count) {
10144 if (buffer == null) {
10145 throw ADP.ArgumentNull(ADP.ParameterBuffer);
10147 if (offset < 0) {
10148 throw ADP.ArgumentOutOfRange(ADP.ParameterOffset);
10150 if (count < 0) {
10151 throw ADP.ArgumentOutOfRange(ADP.ParameterCount);
10153 try {
10154 if (checked(offset + count) > buffer.Length) {
10155 throw ExceptionBuilder.InvalidOffsetLength();
10158 catch (OverflowException) {
10159 // If we've overflowed when adding offset and count, then they never would have fit into buffer anyway
10160 throw ExceptionBuilder.InvalidOffsetLength();
10166 private class ConstrainedTextWriter : TextWriter {
10167 TextWriter _next;
10168 int _size;
10169 int _written;
10171 public ConstrainedTextWriter(TextWriter next, int size) {
10172 _next = next;
10173 _size = size;
10174 _written = 0;
10176 if (_size < 1) {
10177 _size = int.MaxValue;
10181 public bool IsComplete {
10182 get {
10183 return _size > 0 && _written >= _size;
10187 public override Encoding Encoding {
10188 get { return _next.Encoding; }
10191 public override void Flush() {
10192 _next.Flush();
10195 public override Task FlushAsync() {
10196 return _next.FlushAsync();
10199 public override void Write(char value) {
10200 if (_written < _size) {
10201 _next.Write(value);
10202 _written++;
10204 Debug.Assert(_size < 0 || _written <= _size, string.Format("Length of data written exceeds specified length. Written: {0}, specified: {1}", _written, _size));
10207 public override void Write(char[] buffer, int index, int count) {
10209 ValidateWriteParameters(buffer, index, count);
10211 Debug.Assert(_size >= _written);
10212 count = Math.Min(_size - _written, count);
10213 if (count > 0) {
10214 _next.Write(buffer, index, count);
10216 _written += count;
10219 public override Task WriteAsync(char value) {
10221 if (_written < _size) {
10222 _written++;
10223 return _next.WriteAsync(value);
10226 return CompletedTask;
10229 public override Task WriteAsync(char[] buffer, int index, int count) {
10231 ValidateWriteParameters(buffer, index, count);
10233 Debug.Assert(_size >= _written);
10234 count = Math.Min(_size - _written, count);
10235 if (count > 0) {
10236 _written += count;
10237 return _next.WriteAsync(buffer, index, count);
10240 return CompletedTask;
10243 public override Task WriteAsync(string value) {
10244 return WriteAsync(value.ToCharArray());
10247 internal static void ValidateWriteParameters(char[] buffer, int offset, int count) {
10248 if (buffer == null) {
10249 throw ADP.ArgumentNull(ADP.ParameterBuffer);
10251 if (offset < 0) {
10252 throw ADP.ArgumentOutOfRange(ADP.ParameterOffset);
10254 if (count < 0) {
10255 throw ADP.ArgumentOutOfRange(ADP.ParameterCount);
10257 try {
10258 if (checked(offset + count) > buffer.Length) {
10259 throw ExceptionBuilder.InvalidOffsetLength();
10262 catch (OverflowException) {
10263 // If we've overflowed when adding offset and count, then they never would have fit into buffer anyway
10264 throw ExceptionBuilder.InvalidOffsetLength();
10270 private async Task WriteXmlFeed(XmlDataFeed feed, TdsParserStateObject stateObj, bool needBom, Encoding encoding, int size) {
10271 byte[] preambleToSkip = null;
10272 if (!needBom) {
10273 preambleToSkip = encoding.GetPreamble();
10275 ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, preambleToSkip), encoding), size);
10277 XmlWriterSettings writerSettings = new XmlWriterSettings();
10278 writerSettings.CloseOutput = false; // don't close the memory stream
10279 writerSettings.ConformanceLevel = ConformanceLevel.Fragment;
10280 if (_asyncWrite) {
10281 writerSettings.Async = true;
10283 XmlWriter ww = XmlWriter.Create(writer, writerSettings);
10285 if (feed._source.ReadState == ReadState.Initial) {
10286 feed._source.Read();
10289 while (!feed._source.EOF && !writer.IsComplete) {
10291 // We are copying nodes from a reader to a writer. This will cause the
10292 // XmlDeclaration to be emitted despite ConformanceLevel.Fragment above.
10293 // Therefore, we filter out the XmlDeclaration while copying.
10294 if (feed._source.NodeType == XmlNodeType.XmlDeclaration) {
10295 feed._source.Read();
10296 continue;
10299 if (_asyncWrite) {
10300 await ww.WriteNodeAsync(feed._source, true).ConfigureAwait(false);
10302 else {
10303 ww.WriteNode(feed._source, true);
10307 if (_asyncWrite) {
10308 await ww.FlushAsync().ConfigureAwait(false);
10310 else {
10311 ww.Flush();
10315 private async Task WriteTextFeed(TextDataFeed feed, Encoding encoding, bool needBom, TdsParserStateObject stateObj, int size) {
10316 Debug.Assert(encoding == null || !needBom);
10317 char[] inBuff = new char[constTextBufferSize];
10319 encoding = encoding ?? new UnicodeEncoding(false, false);
10320 ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null), encoding), size);
10322 if (needBom) {
10323 if (_asyncWrite) {
10324 await writer.WriteAsync((char)TdsEnums.XMLUNICODEBOM).ConfigureAwait(false);
10326 else {
10327 writer.Write((char)TdsEnums.XMLUNICODEBOM);
10331 int nWritten = 0;
10332 do {
10333 int nRead = 0;
10335 if (_asyncWrite) {
10336 nRead = await feed._source.ReadBlockAsync(inBuff, 0, constTextBufferSize).ConfigureAwait(false);
10338 else {
10339 nRead = feed._source.ReadBlock(inBuff, 0, constTextBufferSize);
10342 if (nRead == 0) {
10343 break;
10346 if (_asyncWrite) {
10347 await writer.WriteAsync(inBuff, 0, nRead).ConfigureAwait(false);
10349 else {
10350 writer.Write(inBuff, 0, nRead);
10353 nWritten += nRead;
10354 } while (!writer.IsComplete);
10356 if (_asyncWrite) {
10357 await writer.FlushAsync().ConfigureAwait(false);
10359 else {
10360 writer.Flush();
10364 private async Task WriteStreamFeed(StreamDataFeed feed, TdsParserStateObject stateObj, int len) {
10365 TdsOutputStream output = new TdsOutputStream(this, stateObj, null);
10366 byte[] buff = new byte[constBinBufferSize];
10367 int nWritten = 0;
10368 do {
10369 int nRead = 0;
10370 int readSize = constBinBufferSize;
10371 if (len > 0 && nWritten + readSize > len) {
10372 readSize = len - nWritten;
10375 Debug.Assert(readSize >= 0);
10377 if (_asyncWrite) {
10378 nRead = await feed._source.ReadAsync(buff, 0, readSize).ConfigureAwait(false);
10380 else {
10381 nRead = feed._source.Read(buff, 0, readSize);
10384 if (nRead == 0) {
10385 return;
10388 if (_asyncWrite) {
10389 await output.WriteAsync(buff, 0, nRead).ConfigureAwait(false);
10391 else {
10392 output.Write(buff, 0, nRead);
10395 nWritten += nRead;
10396 } while (len <= 0 || nWritten < len);
10399 private Task NullIfCompletedWriteTask(Task task) {
10400 if (task == null) {
10401 return null;
10403 switch (task.Status) {
10404 case TaskStatus.RanToCompletion:
10405 return null;
10406 case TaskStatus.Faulted:
10407 throw task.Exception.InnerException;
10408 case TaskStatus.Canceled:
10409 throw SQL.OperationCancelled();
10410 default:
10411 return task;
10415 private Task WriteValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) {
10416 return GetTerminationTask(WriteUnterminatedValue(value, type, scale, actualLength, encodingByteSize, offset, stateObj, paramSize, isDataFeed),
10417 value, type, actualLength, stateObj, isDataFeed);
10420 // For MAX types, this method can only write everything in one big chunk. If multiple
10421 // chunk writes needed, please use WritePlpBytes/WritePlpChars
10422 private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed)
10424 Debug.Assert((null != value) && (DBNull.Value != value), "unexpected missing or empty object");
10426 // parameters are always sent over as BIG or N types
10427 switch (type.NullableType) {
10428 case TdsEnums.SQLFLTN:
10429 if (type.FixedLength == 4)
10430 WriteFloat((Single)value, stateObj);
10431 else {
10432 Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!");
10433 WriteDouble((Double)value, stateObj);
10436 break;
10438 case TdsEnums.SQLBIGBINARY:
10439 case TdsEnums.SQLBIGVARBINARY:
10440 case TdsEnums.SQLIMAGE:
10441 case TdsEnums.SQLUDT: {
10442 // An array should be in the object
10443 Debug.Assert(isDataFeed || value is byte[], "Value should be an array of bytes");
10444 Debug.Assert(!isDataFeed || value is StreamDataFeed, "Value should be a stream");
10446 if (isDataFeed) {
10447 Debug.Assert(type.IsPlp,"Stream assigned to non-PLP was not converted!");
10448 return NullIfCompletedWriteTask(WriteStreamFeed((StreamDataFeed)value, stateObj, paramSize));
10450 else {
10451 if (type.IsPlp) {
10452 WriteInt(actualLength, stateObj); // chunk length
10455 return stateObj.WriteByteArray((byte[])value, actualLength, offset, canAccumulate: false);
10459 case TdsEnums.SQLUNIQUEID: {
10460 System.Guid guid = (System.Guid)value;
10461 byte[] b = guid.ToByteArray();
10463 Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object");
10464 stateObj.WriteByteArray(b, actualLength, 0);
10465 break;
10468 case TdsEnums.SQLBITN: {
10469 Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type");
10470 if ((bool)value == true)
10471 stateObj.WriteByte(1);
10472 else
10473 stateObj.WriteByte(0);
10475 break;
10478 case TdsEnums.SQLINTN:
10479 if (type.FixedLength == 1)
10480 stateObj.WriteByte((byte)value);
10481 else if (type.FixedLength == 2)
10482 WriteShort((Int16)value, stateObj);
10483 else if (type.FixedLength == 4)
10484 WriteInt((Int32)value, stateObj);
10485 else {
10486 Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture));
10487 WriteLong((Int64)value, stateObj);
10490 break;
10492 case TdsEnums.SQLBIGCHAR:
10493 case TdsEnums.SQLBIGVARCHAR:
10494 case TdsEnums.SQLTEXT: {
10495 Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader");
10496 Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string");
10498 if (isDataFeed) {
10499 Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!");
10500 TextDataFeed tdf = value as TextDataFeed;
10501 if (tdf == null) {
10502 return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, needBom:true, encoding:_defaultEncoding, size:paramSize));
10504 else {
10505 return NullIfCompletedWriteTask(WriteTextFeed(tdf, _defaultEncoding, false, stateObj, paramSize));
10508 else {
10509 if (type.IsPlp) {
10510 WriteInt(encodingByteSize, stateObj); // chunk length
10512 if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value
10513 return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false);
10515 else {
10516 return WriteEncodingChar((string)value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false);
10520 case TdsEnums.SQLNCHAR:
10521 case TdsEnums.SQLNVARCHAR:
10522 case TdsEnums.SQLNTEXT:
10523 case TdsEnums.SQLXMLTYPE: {
10524 Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader");
10525 Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string");
10527 if (isDataFeed) {
10528 Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!");
10529 TextDataFeed tdf = value as TextDataFeed;
10530 if (tdf == null) {
10531 return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize));
10533 else {
10534 return NullIfCompletedWriteTask(WriteTextFeed(tdf, null, IsBOMNeeded(type, value), stateObj, paramSize));
10537 else {
10538 if (type.IsPlp) {
10539 if (IsBOMNeeded(type, value)) {
10540 WriteInt(actualLength + 2, stateObj); // chunk length
10541 WriteShort(TdsEnums.XMLUNICODEBOM, stateObj);
10543 else {
10544 WriteInt(actualLength, stateObj); // chunk length
10547 if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value
10548 return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false);
10550 else {
10551 // convert to cchars instead of cbytes
10552 actualLength >>= 1;
10553 return WriteString((string)value, actualLength, offset, stateObj, canAccumulate: false);
10557 case TdsEnums.SQLNUMERICN:
10558 Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes");
10559 WriteDecimal((Decimal)value, stateObj);
10560 break;
10562 case TdsEnums.SQLDATETIMN:
10563 Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length");
10565 TdsDateTime dt = MetaType.FromDateTime((DateTime)value, (byte)type.FixedLength);
10567 if (type.FixedLength == 4) {
10568 if (0 > dt.days || dt.days > UInt16.MaxValue)
10569 throw SQL.SmallDateTimeOverflow(MetaType.ToDateTime(dt.days, dt.time, 4).ToString(CultureInfo.InvariantCulture));
10571 WriteShort(dt.days, stateObj);
10572 WriteShort(dt.time, stateObj);
10574 else {
10575 WriteInt(dt.days, stateObj);
10576 WriteInt(dt.time, stateObj);
10579 break;
10581 case TdsEnums.SQLMONEYN: {
10582 WriteCurrency((Decimal)value, type.FixedLength, stateObj);
10583 break;
10586 case TdsEnums.SQLDATE: {
10587 WriteDate((DateTime)value, stateObj);
10588 break;
10591 case TdsEnums.SQLTIME:
10592 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10593 throw SQL.TimeScaleValueOutOfRange(scale);
10595 WriteTime((TimeSpan)value, scale, actualLength, stateObj);
10596 break;
10598 case TdsEnums.SQLDATETIME2:
10599 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10600 throw SQL.TimeScaleValueOutOfRange(scale);
10602 WriteDateTime2((DateTime)value, scale, actualLength, stateObj);
10603 break;
10605 case TdsEnums.SQLDATETIMEOFFSET:
10606 WriteDateTimeOffset((DateTimeOffset)value, scale, actualLength, stateObj);
10607 break;
10609 default:
10610 Debug.Assert(false, "Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null));
10611 break;
10612 } // switch
10613 // return point for accumualated writes, note: non-accumulated writes returned from their case statements
10614 return null;
10615 // Debug.WriteLine("value: " + value.ToString(CultureInfo.InvariantCulture));
10618 /// <summary>
10619 /// Write parameter encryption metadata and returns a task if necessary.
10620 /// </summary>
10621 private Task WriteEncryptionMetadata(Task terminatedWriteTask, SqlColumnEncryptionInputParameterInfo columnEncryptionParameterInfo, TdsParserStateObject stateObj) {
10622 Debug.Assert(columnEncryptionParameterInfo != null, @"columnEncryptionParameterInfo cannot be null");
10623 Debug.Assert(stateObj != null, @"stateObj cannot be null");
10625 // If there is not task already, simply write the encryption metadata synchronously.
10626 if (terminatedWriteTask == null) {
10627 WriteEncryptionMetadata(columnEncryptionParameterInfo, stateObj);
10628 return null;
10630 else {
10631 // Otherwise, create a continuation task to write the encryption metadata after the previous write completes.
10632 return AsyncHelper.CreateContinuationTask<SqlColumnEncryptionInputParameterInfo, TdsParserStateObject>(terminatedWriteTask,
10633 WriteEncryptionMetadata, columnEncryptionParameterInfo, stateObj,
10634 connectionToDoom: _connHandler);
10638 /// <summary>
10639 /// Write parameter encryption metadata.
10640 /// </summary>
10641 private void WriteEncryptionMetadata(SqlColumnEncryptionInputParameterInfo columnEncryptionParameterInfo, TdsParserStateObject stateObj) {
10642 Debug.Assert(columnEncryptionParameterInfo != null, @"columnEncryptionParameterInfo cannot be null");
10643 Debug.Assert(stateObj != null, @"stateObj cannot be null");
10645 // Write the TypeInfo.
10646 WriteSmiTypeInfo(columnEncryptionParameterInfo.ParameterMetadata, stateObj);
10648 // Write the serialized array in columnEncryptionParameterInfo.
10649 stateObj.WriteByteArray(columnEncryptionParameterInfo.SerializedWireFormat,
10650 columnEncryptionParameterInfo.SerializedWireFormat.Length,
10651 offsetBuffer: 0);
10654 // For MAX types, this method can only write everything in one big chunk. If multiple
10655 // chunk writes needed, please use WritePlpBytes/WritePlpChars
10656 private byte[] SerializeUnencryptedValue(object value, MetaType type, byte scale, int actualLength, int offset, bool isDataFeed, byte normalizationVersion, TdsParserStateObject stateObj) {
10657 Debug.Assert((null != value) && (DBNull.Value != value), "unexpected missing or empty object");
10659 if (normalizationVersion != 0x01) {
10660 throw SQL.UnsupportedNormalizationVersion(normalizationVersion);
10663 // parameters are always sent over as BIG or N types
10664 switch (type.NullableType) {
10665 case TdsEnums.SQLFLTN:
10666 if (type.FixedLength == 4)
10667 return SerializeFloat((Single)value);
10668 else {
10669 Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!");
10670 return SerializeDouble((Double)value);
10673 case TdsEnums.SQLBIGBINARY:
10674 case TdsEnums.SQLBIGVARBINARY:
10675 case TdsEnums.SQLIMAGE:
10676 case TdsEnums.SQLUDT: {
10677 Debug.Assert(!isDataFeed, "We cannot seriliaze streams");
10678 Debug.Assert(value is byte[], "Value should be an array of bytes");
10680 byte[] b = new byte[actualLength];
10681 Buffer.BlockCopy((byte[])value, offset, b, 0, actualLength);
10682 return b;
10685 case TdsEnums.SQLUNIQUEID: {
10686 System.Guid guid = (System.Guid)value;
10687 byte[] b = guid.ToByteArray();
10689 Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object");
10690 return b;
10693 case TdsEnums.SQLBITN: {
10694 Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type");
10696 // We normalize to allow conversion across data types. BIT is serialized into a BIGINT.
10697 return SerializeLong((bool)value == true ? 1 : 0, stateObj);
10700 case TdsEnums.SQLINTN:
10701 if (type.FixedLength == 1)
10702 return SerializeLong((byte)value, stateObj);
10704 if (type.FixedLength == 2)
10705 return SerializeLong((Int16)value, stateObj);
10707 if (type.FixedLength == 4)
10708 return SerializeLong((Int32)value, stateObj);
10710 Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture));
10711 return SerializeLong((Int64)value, stateObj);
10713 case TdsEnums.SQLBIGCHAR:
10714 case TdsEnums.SQLBIGVARCHAR:
10715 case TdsEnums.SQLTEXT: {
10716 Debug.Assert(!isDataFeed, "We cannot seriliaze streams");
10717 Debug.Assert((value is string || value is byte[]), "Value is a byte array or string");
10719 if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value
10720 byte[] b = new byte[actualLength];
10721 Buffer.BlockCopy((byte[])value, 0, b, 0, actualLength);
10722 return b;
10724 else {
10725 return SerializeEncodingChar((string)value, actualLength, offset, _defaultEncoding);
10728 case TdsEnums.SQLNCHAR:
10729 case TdsEnums.SQLNVARCHAR:
10730 case TdsEnums.SQLNTEXT:
10731 case TdsEnums.SQLXMLTYPE: {
10732 Debug.Assert(!isDataFeed, "We cannot seriliaze streams");
10733 Debug.Assert((value is string || value is byte[]), "Value is a byte array or string");
10735 if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value
10736 byte[] b = new byte[actualLength];
10737 Buffer.BlockCopy((byte[])value, 0, b, 0, actualLength);
10738 return b;
10740 else { // convert to cchars instead of cbytes
10741 actualLength >>= 1;
10742 return SerializeString((string)value, actualLength, offset);
10745 case TdsEnums.SQLNUMERICN:
10746 Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes");
10747 return SerializeDecimal((Decimal)value, stateObj);
10749 case TdsEnums.SQLDATETIMN:
10750 Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length");
10752 TdsDateTime dt = MetaType.FromDateTime((DateTime)value, (byte)type.FixedLength);
10754 if (type.FixedLength == 4) {
10755 if (0 > dt.days || dt.days > UInt16.MaxValue)
10756 throw SQL.SmallDateTimeOverflow(MetaType.ToDateTime(dt.days, dt.time, 4).ToString(CultureInfo.InvariantCulture));
10758 if (null == stateObj._bIntBytes) {
10759 stateObj._bIntBytes = new byte[4];
10762 byte[] b = stateObj._bIntBytes;
10763 int current = 0;
10765 byte[] bPart = SerializeShort(dt.days, stateObj);
10766 Buffer.BlockCopy(bPart, 0, b, current, 2);
10767 current += 2;
10769 bPart = SerializeShort(dt.time, stateObj);
10770 Buffer.BlockCopy(bPart, 0, b, current, 2);
10772 return b;
10774 else {
10775 if (null == stateObj._bLongBytes) {
10776 stateObj._bLongBytes = new byte[8];
10778 byte[] b = stateObj._bLongBytes;
10779 int current = 0;
10781 byte[] bPart = SerializeInt(dt.days, stateObj);
10782 Buffer.BlockCopy(bPart, 0, b, current, 4);
10783 current += 4;
10785 bPart = SerializeInt(dt.time, stateObj);
10786 Buffer.BlockCopy(bPart, 0, b, current, 4);
10788 return b;
10791 case TdsEnums.SQLMONEYN: {
10792 return SerializeCurrency((Decimal)value, type.FixedLength, stateObj);
10795 case TdsEnums.SQLDATE: {
10796 return SerializeDate((DateTime)value);
10799 case TdsEnums.SQLTIME:
10800 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10801 throw SQL.TimeScaleValueOutOfRange(scale);
10803 return SerializeTime((TimeSpan)value, scale, actualLength);
10805 case TdsEnums.SQLDATETIME2:
10806 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10807 throw SQL.TimeScaleValueOutOfRange(scale);
10809 return SerializeDateTime2((DateTime)value, scale, actualLength);
10811 case TdsEnums.SQLDATETIMEOFFSET:
10812 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10813 throw SQL.TimeScaleValueOutOfRange(scale);
10815 return SerializeDateTimeOffset((DateTimeOffset)value, scale, actualLength);
10817 default:
10818 throw SQL.UnsupportedDatatypeEncryption(type.TypeName);
10819 } // switch
10820 // Debug.WriteLine("value: " + value.ToString(CultureInfo.InvariantCulture));
10823 // For MAX types, this method can only write everything in one big chunk. If multiple
10824 // chunk writes needed, please use WritePlpBytes/WritePlpChars
10825 private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int actualLength, int offset, byte normalizationVersion, TdsParserStateObject stateObj) {
10826 Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) ||
10827 (value is INullable && !((INullable)value).IsNull)),
10828 "unexpected null SqlType!");
10830 if (normalizationVersion != 0x01) {
10831 throw SQL.UnsupportedNormalizationVersion(normalizationVersion);
10834 // parameters are always sent over as BIG or N types
10835 switch (type.NullableType) {
10836 case TdsEnums.SQLFLTN:
10837 if (type.FixedLength == 4)
10838 return SerializeFloat(((SqlSingle)value).Value);
10839 else {
10840 Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!");
10841 return SerializeDouble(((SqlDouble)value).Value);
10844 case TdsEnums.SQLBIGBINARY:
10845 case TdsEnums.SQLBIGVARBINARY:
10846 case TdsEnums.SQLIMAGE: {
10847 byte[] b = new byte[actualLength];
10849 if (value is SqlBinary) {
10850 Buffer.BlockCopy(((SqlBinary)value).Value, offset, b, 0, actualLength);
10852 else {
10853 Debug.Assert(value is SqlBytes);
10854 Buffer.BlockCopy(((SqlBytes)value).Value, offset, b, 0, actualLength);
10856 return b;
10859 case TdsEnums.SQLUNIQUEID: {
10860 byte[] b = ((SqlGuid)value).ToByteArray();
10862 Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object");
10863 return b;
10866 case TdsEnums.SQLBITN: {
10867 Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type");
10869 // We normalize to allow conversion across data types. BIT is serialized into a BIGINT.
10870 return SerializeLong(((SqlBoolean)value).Value == true ? 1 : 0, stateObj);
10873 case TdsEnums.SQLINTN:
10874 // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT.
10875 if (type.FixedLength == 1)
10876 return SerializeLong(((SqlByte)value).Value, stateObj);
10878 if (type.FixedLength == 2)
10879 return SerializeLong(((SqlInt16)value).Value, stateObj);
10881 if (type.FixedLength == 4)
10882 return SerializeLong(((SqlInt32)value).Value, stateObj);
10883 else {
10884 Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture));
10885 return SerializeLong(((SqlInt64)value).Value, stateObj);
10888 case TdsEnums.SQLBIGCHAR:
10889 case TdsEnums.SQLBIGVARCHAR:
10890 case TdsEnums.SQLTEXT:
10891 if (value is SqlChars) {
10892 String sch = new String(((SqlChars)value).Value);
10893 return SerializeEncodingChar(sch, actualLength, offset, _defaultEncoding);
10895 else {
10896 Debug.Assert(value is SqlString);
10897 return SerializeEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding);
10901 case TdsEnums.SQLNCHAR:
10902 case TdsEnums.SQLNVARCHAR:
10903 case TdsEnums.SQLNTEXT:
10904 case TdsEnums.SQLXMLTYPE:
10905 // convert to cchars instead of cbytes
10906 // Xml type is already converted to string through GetCoercedValue
10907 if (actualLength != 0)
10908 actualLength >>= 1;
10910 if (value is SqlChars) {
10911 return SerializeCharArray(((SqlChars)value).Value, actualLength, offset);
10913 else {
10914 Debug.Assert(value is SqlString);
10915 return SerializeString(((SqlString)value).Value, actualLength, offset);
10918 case TdsEnums.SQLNUMERICN:
10919 Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes");
10920 return SerializeSqlDecimal((SqlDecimal)value, stateObj);
10922 case TdsEnums.SQLDATETIMN:
10923 SqlDateTime dt = (SqlDateTime)value;
10925 if (type.FixedLength == 4) {
10926 if (0 > dt.DayTicks || dt.DayTicks > UInt16.MaxValue)
10927 throw SQL.SmallDateTimeOverflow(dt.ToString());
10929 if (null == stateObj._bIntBytes) {
10930 stateObj._bIntBytes = new byte[4];
10933 byte[] b = stateObj._bIntBytes;
10934 int current = 0;
10936 byte[] bPart = SerializeShort(dt.DayTicks, stateObj);
10937 Buffer.BlockCopy(bPart, 0, b, current, 2);
10938 current += 2;
10940 bPart = SerializeShort(dt.TimeTicks / SqlDateTime.SQLTicksPerMinute, stateObj);
10941 Buffer.BlockCopy(bPart, 0, b, current, 2);
10943 return b;
10945 else {
10946 if (null == stateObj._bLongBytes) {
10947 stateObj._bLongBytes = new byte[8];
10950 byte[] b = stateObj._bLongBytes;
10951 int current = 0;
10953 byte[] bPart = SerializeInt(dt.DayTicks, stateObj);
10954 Buffer.BlockCopy(bPart, 0, b, current, 4);
10955 current += 4;
10957 bPart = SerializeInt(dt.TimeTicks, stateObj);
10958 Buffer.BlockCopy(bPart, 0, b, current, 4);
10960 return b;
10963 case TdsEnums.SQLMONEYN: {
10964 return SerializeSqlMoney((SqlMoney)value, type.FixedLength, stateObj);
10967 default:
10968 throw SQL.UnsupportedDatatypeEncryption(type.TypeName);
10969 } // switch
10973 // we always send over nullable types for parameters so we always write the varlen fields
10976 internal void WriteParameterVarLen(MetaType type, int size, bool isNull, TdsParserStateObject stateObj, bool unknownLength=false) {
10977 if (type.IsLong) { // text/image/SQLVariant have a 4 byte length, plp datatypes have 8 byte lengths
10978 if (isNull) {
10979 if (type.IsPlp) {
10980 WriteLong(unchecked((long)TdsEnums.SQL_PLP_NULL), stateObj);
10982 else {
10983 WriteInt(unchecked((int)TdsEnums.VARLONGNULL), stateObj);
10986 else if (type.NullableType == TdsEnums.SQLXMLTYPE || unknownLength) {
10987 WriteUnsignedLong(TdsEnums.SQL_PLP_UNKNOWNLEN, stateObj);
10989 else if (type.IsPlp) {
10990 // Non-xml plp types
10991 WriteLong((long)size, stateObj);
10993 else {
10994 WriteInt(size, stateObj);
10997 else if (type.IsVarTime) {
10998 if (isNull) {
10999 stateObj.WriteByte(TdsEnums.FIXEDNULL);
11001 else {
11002 stateObj.WriteByte((byte)size);
11005 else if (false == type.IsFixed) { // non-long but variable length column, must be a BIG* type: 2 byte length
11006 if (isNull) {
11007 WriteShort(TdsEnums.VARNULL, stateObj);
11009 else {
11010 WriteShort(size, stateObj);
11013 else {
11014 if (isNull) {
11015 stateObj.WriteByte(TdsEnums.FIXEDNULL);
11017 else {
11018 Debug.Assert(type.FixedLength <= 0xff, "WriteParameterVarLen: invalid one byte length!");
11019 stateObj.WriteByte((byte)(type.FixedLength & 0xff)); // 1 byte for everything else
11024 // Reads the next chunk in a nvarchar(max) data stream.
11025 // This call must be preceeded by a call to ReadPlpLength or ReadDataLength.
11026 // Will not start reading into the next chunk if bytes requested is larger than
11027 // the current chunk length. Do another ReadPlpLength, ReadPlpUnicodeChars in that case.
11028 // Returns the actual chars read
11029 private bool TryReadPlpUnicodeCharsChunk(char[] buff, int offst, int len, TdsParserStateObject stateObj, out int charsRead) {
11031 Debug.Assert((buff == null && len == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!");
11032 Debug.Assert((stateObj._longlen != 0) && (stateObj._longlen != TdsEnums.SQL_PLP_NULL),
11033 "Out of sync plp read request");
11034 if (stateObj._longlenleft == 0) {
11035 Debug.Assert(false, "Out of sync read request");
11036 charsRead = 0;
11037 return true;
11040 charsRead = len;
11042 // stateObj._longlenleft is in bytes
11043 if ((stateObj._longlenleft >> 1) < (ulong)len)
11044 charsRead = (int)(stateObj._longlenleft >> 1);
11046 for (int ii = 0; ii < charsRead; ii++) {
11047 if (!stateObj.TryReadChar(out buff[offst + ii])) {
11048 return false;
11052 stateObj._longlenleft -= ((ulong)charsRead << 1);
11053 return true;
11056 internal int ReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserStateObject stateObj) {
11057 int charsRead;
11058 Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
11059 bool result = TryReadPlpUnicodeChars(ref buff, offst, len, stateObj, out charsRead);
11060 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
11061 return charsRead;
11064 // Reads the requested number of chars from a plp data stream, or the entire data if
11065 // requested length is -1 or larger than the actual length of data. First call to this method
11066 // should be preceeded by a call to ReadPlpLength or ReadDataLength.
11067 // Returns the actual chars read.
11068 internal bool TryReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserStateObject stateObj, out int totalCharsRead) {
11069 int charsRead = 0;
11070 int charsLeft = 0;
11071 char[] newbuf;
11073 if (stateObj._longlen == 0) {
11074 Debug.Assert(stateObj._longlenleft == 0);
11075 totalCharsRead = 0;
11076 return true; // No data
11079 Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL),
11080 "Out of sync plp read request");
11082 Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!");
11083 charsLeft = len;
11085 // If total length is known up front, allocate the whole buffer in one shot instead of realloc'ing and copying over each time
11086 if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN) {
11087 buff = new char[(int)Math.Min((int)stateObj._longlen, len)];
11090 if (stateObj._longlenleft == 0) {
11091 ulong ignored;
11092 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11093 totalCharsRead = 0;
11094 return false;
11096 if (stateObj._longlenleft == 0) { // Data read complete
11097 totalCharsRead = 0;
11098 return true;
11102 totalCharsRead = 0;
11104 while (charsLeft > 0) {
11105 charsRead = (int)Math.Min((stateObj._longlenleft + 1) >> 1, (ulong)charsLeft);
11106 if ((buff == null) || (buff.Length < (offst + charsRead))) {
11107 // Grow the array
11108 newbuf = new char[offst + charsRead];
11109 if (buff != null) {
11110 Buffer.BlockCopy(buff, 0, newbuf, 0, offst*2);
11112 buff = newbuf;
11114 if (charsRead > 0) {
11115 if (!TryReadPlpUnicodeCharsChunk(buff, offst, charsRead, stateObj, out charsRead)) {
11116 return false;
11118 charsLeft -= charsRead;
11119 offst += charsRead;
11120 totalCharsRead += charsRead;
11122 // Special case single byte left
11123 if (stateObj._longlenleft == 1 && (charsLeft > 0)) {
11124 byte b1;
11125 if (!stateObj.TryReadByte(out b1)) {
11126 return false;
11128 stateObj._longlenleft--;
11129 ulong ignored;
11130 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11131 return false;
11133 Debug.Assert((stateObj._longlenleft != 0), "ReadPlpUnicodeChars: Odd byte left at the end!");
11134 byte b2;
11135 if (!stateObj.TryReadByte(out b2)) {
11136 return false;
11138 stateObj._longlenleft--;
11139 // Put it at the end of the array. At this point we know we have an extra byte.
11140 buff[offst] = (char)(((b2 & 0xff) << 8) + (b1 & 0xff));
11141 offst = checked((int)offst + 1);
11142 charsRead++;
11143 charsLeft--;
11144 totalCharsRead++;
11146 if (stateObj._longlenleft == 0) { // Read the next chunk or cleanup state if hit the end
11147 ulong ignored;
11148 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11149 return false;
11153 if (stateObj._longlenleft == 0) // Data read complete
11154 break;
11156 return true;
11159 internal int ReadPlpAnsiChars(ref char[] buff, int offst, int len, SqlMetaDataPriv metadata, TdsParserStateObject stateObj) {
11160 int charsRead = 0;
11161 int charsLeft = 0;
11162 int bytesRead = 0;
11163 int totalcharsRead = 0;
11165 if (stateObj._longlen == 0) {
11166 Debug.Assert(stateObj._longlenleft == 0);
11167 return 0; // No data
11170 Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL),
11171 "Out of sync plp read request");
11173 Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpAnsiChars()!");
11174 charsLeft = len;
11176 if (stateObj._longlenleft == 0) {
11177 stateObj.ReadPlpLength(false);
11178 if (stateObj._longlenleft == 0) {// Data read complete
11179 stateObj._plpdecoder = null;
11180 return 0;
11184 if (stateObj._plpdecoder == null) {
11185 Encoding enc = metadata.encoding;
11187 if (enc == null)
11189 if (null == _defaultEncoding) {
11190 ThrowUnsupportedCollationEncountered(stateObj);
11194 enc = _defaultEncoding;
11196 stateObj._plpdecoder = enc.GetDecoder();
11199 while (charsLeft > 0) {
11200 bytesRead = (int)Math.Min(stateObj._longlenleft, (ulong)charsLeft);
11201 if ((stateObj._bTmp == null) || (stateObj._bTmp.Length < bytesRead)) {
11202 // Grow the array
11203 stateObj._bTmp = new byte[bytesRead];
11206 bytesRead = stateObj.ReadPlpBytesChunk(stateObj._bTmp, 0, bytesRead);
11208 charsRead = stateObj._plpdecoder.GetChars(stateObj._bTmp, 0, bytesRead, buff, offst);
11209 charsLeft -= charsRead;
11210 offst += charsRead;
11211 totalcharsRead += charsRead;
11212 if (stateObj._longlenleft == 0) // Read the next chunk or cleanup state if hit the end
11213 stateObj.ReadPlpLength(false);
11215 if (stateObj._longlenleft == 0) { // Data read complete
11216 stateObj._plpdecoder = null;
11217 break;
11220 return (totalcharsRead);
11223 // ensure value is not null and does not have an NBC bit set for it before using this method
11224 internal ulong SkipPlpValue(ulong cb, TdsParserStateObject stateObj) {
11225 ulong skipped;
11226 Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
11227 bool result = TrySkipPlpValue(cb, stateObj, out skipped);
11228 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
11229 return skipped;
11232 internal bool TrySkipPlpValue(ulong cb, TdsParserStateObject stateObj, out ulong totalBytesSkipped) {
11233 // Read and skip cb bytes or until ReadPlpLength returns 0.
11234 int bytesSkipped;
11235 totalBytesSkipped = 0;
11237 if (stateObj._longlenleft == 0) {
11238 ulong ignored;
11239 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11240 return false;
11244 while ((totalBytesSkipped < cb) &&
11245 (stateObj._longlenleft > 0)) {
11246 if (stateObj._longlenleft > Int32.MaxValue)
11247 bytesSkipped = Int32.MaxValue;
11248 else
11249 bytesSkipped = (int)stateObj._longlenleft;
11250 bytesSkipped = ((cb - totalBytesSkipped) < (ulong) bytesSkipped) ? (int)(cb - totalBytesSkipped) : bytesSkipped;
11252 if (!stateObj.TrySkipBytes(bytesSkipped)) {
11253 return false;
11255 stateObj._longlenleft -= (ulong)bytesSkipped;
11256 totalBytesSkipped += (ulong)bytesSkipped;
11258 if (stateObj._longlenleft == 0) {
11259 ulong ignored;
11260 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11261 return false;
11266 return true;
11269 internal ulong PlpBytesLeft(TdsParserStateObject stateObj) {
11270 if ((stateObj._longlen != 0) && (stateObj._longlenleft == 0))
11271 stateObj.ReadPlpLength(false);
11273 return stateObj._longlenleft;
11276 internal bool TryPlpBytesLeft(TdsParserStateObject stateObj, out ulong left) {
11277 if ((stateObj._longlen != 0) && (stateObj._longlenleft == 0)) {
11278 if (!stateObj.TryReadPlpLength(false, out left)) {
11279 return false;
11283 left = stateObj._longlenleft;
11284 return true;
11287 private const ulong _indeterminateSize = 0xffffffffffffffff; // Represents unknown size
11289 internal ulong PlpBytesTotalLength(TdsParserStateObject stateObj) {
11290 if (stateObj._longlen == TdsEnums.SQL_PLP_UNKNOWNLEN)
11291 return _indeterminateSize;
11292 else if (stateObj._longlen == TdsEnums.SQL_PLP_NULL)
11293 return 0;
11295 return stateObj._longlen;
11298 const string StateTraceFormatString = "\n\t"
11299 + " _physicalStateObj = {0}\n\t"
11300 + " _pMarsPhysicalConObj = {1}\n\t"
11301 + " _state = {2}\n\t"
11302 + " _server = {3}\n\t"
11303 + " _fResetConnection = {4}\n\t"
11304 + " _defaultCollation = {5}\n\t"
11305 + " _defaultCodePage = {6}\n\t"
11306 + " _defaultLCID = {7}\n\t"
11307 + " _defaultEncoding = {8}\n\t"
11308 + " _encryptionOption = {10}\n\t"
11309 + " _currentTransaction = {11}\n\t"
11310 + " _pendingTransaction = {12}\n\t"
11311 + " _retainedTransactionId = {13}\n\t"
11312 + " _nonTransactedOpenResultCount = {14}\n\t"
11313 + " _connHandler = {15}\n\t"
11314 + " _fMARS = {16}\n\t"
11315 + " _sessionPool = {17}\n\t"
11316 + " _isShiloh = {18}\n\t"
11317 + " _isShilohSP1 = {19}\n\t"
11318 + " _isYukon = {20}\n\t"
11319 + " _sniSpnBuffer = {21}\n\t"
11320 + " _errors = {22}\n\t"
11321 + " _warnings = {23}\n\t"
11322 + " _attentionErrors = {24}\n\t"
11323 + " _attentionWarnings = {25}\n\t"
11324 + " _statistics = {26}\n\t"
11325 + " _statisticsIsInTransaction = {27}\n\t"
11326 + " _fPreserveTransaction = {28}"
11327 + " _fParallel = {29}"
11329 internal string TraceString() {
11330 return String.Format(/*IFormatProvider*/ null,
11331 StateTraceFormatString,
11332 null == _physicalStateObj,
11333 null == _pMarsPhysicalConObj,
11334 _state,
11335 _server,
11336 _fResetConnection,
11337 null == _defaultCollation ? "(null)" : _defaultCollation.TraceString(),
11338 _defaultCodePage,
11339 _defaultLCID,
11340 TraceObjectClass(_defaultEncoding),
11342 _encryptionOption,
11343 null == _currentTransaction ? "(null)" : _currentTransaction.TraceString(),
11344 null == _pendingTransaction ? "(null)" : _pendingTransaction.TraceString(),
11345 _retainedTransactionId,
11346 _nonTransactedOpenResultCount,
11347 null == _connHandler ? "(null)" : _connHandler.ObjectID.ToString((IFormatProvider)null),
11348 _fMARS,
11349 null == _sessionPool ? "(null)" : _sessionPool.TraceString(),
11350 _isShiloh,
11351 _isShilohSP1,
11352 _isYukon,
11353 null == _sniSpnBuffer ? "(null)" : _sniSpnBuffer.Length.ToString((IFormatProvider)null),
11354 _physicalStateObj != null ? "(null)" : _physicalStateObj.ErrorCount.ToString((IFormatProvider)null),
11355 _physicalStateObj != null ? "(null)" : _physicalStateObj.WarningCount.ToString((IFormatProvider)null),
11356 _physicalStateObj != null ? "(null)" : _physicalStateObj.PreAttentionErrorCount.ToString((IFormatProvider)null),
11357 _physicalStateObj != null ? "(null)" : _physicalStateObj.PreAttentionWarningCount.ToString((IFormatProvider)null),
11358 null == _statistics,
11359 _statisticsIsInTransaction,
11360 _fPreserveTransaction,
11361 null == _connHandler ? "(null)" : _connHandler.ConnectionOptions.MultiSubnetFailover.ToString((IFormatProvider)null),
11362 null == _connHandler ? "(null)" : _connHandler.ConnectionOptions.TransparentNetworkIPResolution.ToString((IFormatProvider)null));
11365 private string TraceObjectClass(object instance) {
11366 if (null == instance) {
11367 return "(null)";
11369 else {
11370 return instance.GetType().ToString();
11373 } // tdsparser
11374 }//namespace