[bcl] Updates referencesource to 4.7.1
[mono-project.git] / mcs / class / referencesource / System.Data / System / Data / SqlClient / TdsParser.cs
blobbc0bb7e3584eac2da6676a422a99ca36f91c0d72
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;
859 Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
860 bool result = _physicalStateObj.TryReadNetworkPacket();
861 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
863 if (_physicalStateObj._inBytesRead == 0) {
864 // If the server did not respond then something has gone wrong and we need to close the connection
865 _physicalStateObj.AddError(new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.PreloginError(), "", 0));
866 _physicalStateObj.Dispose();
867 ThrowExceptionAndWarning(_physicalStateObj);
870 if (!_physicalStateObj.TryProcessHeader()) { throw SQL.SynchronousCallMayNotPend(); }
872 if (_physicalStateObj._inBytesPacket > TdsEnums.MAX_PACKET_SIZE || _physicalStateObj._inBytesPacket <= 0) {
873 throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
875 byte[] payload = new byte[_physicalStateObj._inBytesPacket];
877 Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
878 result = _physicalStateObj.TryReadByteArray(payload, 0, payload.Length);
879 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
881 if (payload[0] == 0xaa) {
882 // If the first byte is 0xAA, we are connecting to a 6.5 or earlier server, which
883 // is not supported. SQL BU DT 296425
884 throw SQL.InvalidSQLServerVersionUnknown();
887 int offset = 0;
888 int payloadOffset = 0;
889 int payloadLength = 0;
890 int option = payload[offset++];
892 while (option != (byte)PreLoginOptions.LASTOPT) {
893 switch (option) {
894 case (int)PreLoginOptions.VERSION:
895 payloadOffset = payload[offset++] << 8 | payload[offset++];
896 payloadLength = payload[offset++] << 8 | payload[offset++];
898 byte majorVersion = payload[payloadOffset];
899 byte minorVersion = payload[payloadOffset + 1];
900 int level = (payload[payloadOffset + 2] << 8) |
901 payload[payloadOffset + 3];
903 isYukonOrLater = majorVersion >= 9;
904 if (!isYukonOrLater) {
905 marsCapable = false; // If pre-Yukon, MARS not supported.
908 break;
910 case (int)PreLoginOptions.ENCRYPT:
911 payloadOffset = payload[offset++] << 8 | payload[offset++];
912 payloadLength = payload[offset++] << 8 | payload[offset++];
914 EncryptionOptions serverOption = (EncryptionOptions)payload[payloadOffset];
916 /* internal enum EncryptionOptions {
917 OFF,
919 NOT_SUP,
920 REQ,
921 LOGIN
922 } */
924 switch (_encryptionOption) {
925 case (EncryptionOptions.ON):
926 if (serverOption == EncryptionOptions.NOT_SUP) {
927 _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0));
928 _physicalStateObj.Dispose();
929 ThrowExceptionAndWarning(_physicalStateObj);
932 break;
934 case (EncryptionOptions.OFF):
935 if (serverOption == EncryptionOptions.OFF) {
936 // Only encrypt login.
937 _encryptionOption = EncryptionOptions.LOGIN;
939 else if (serverOption == EncryptionOptions.REQ) {
940 // Encrypt all.
941 _encryptionOption = EncryptionOptions.ON;
944 break;
946 case (EncryptionOptions.NOT_SUP):
947 if (serverOption == EncryptionOptions.REQ) {
948 _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0));
949 _physicalStateObj.Dispose();
950 ThrowExceptionAndWarning(_physicalStateObj);
953 break;
955 default:
956 Debug.Assert(false, "Invalid client encryption option detected");
957 break;
960 if (_encryptionOption == EncryptionOptions.ON ||
961 _encryptionOption == EncryptionOptions.LOGIN) {
962 UInt32 error = 0;
964 // If we're using legacy server certificate validation behavior (Authentication keyword not provided and not using access token), then validate if
965 // Encrypt=true and Trust Sever Certificate = false.
966 // If using Authentication keyword or access token, validate if Trust Server Certificate=false.
967 bool shouldValidateServerCert = (encrypt && !trustServerCert) || ((authType != SqlAuthenticationMethod.NotSpecified || _connHandler._accessTokenInBytes != null) && !trustServerCert);
969 UInt32 info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0)
970 | (isYukonOrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0);
972 if (encrypt && !integratedSecurity) {
973 // optimization: in case of SQL Authentication and encryption, set SNI_SSL_IGNORE_CHANNEL_BINDINGS to let SNI
974 // know that it does not need to allocate/retrieve the Channel Bindings from the SSL context.
975 info |= TdsEnums.SNI_SSL_IGNORE_CHANNEL_BINDINGS;
978 // Add SSL (Encryption) SNI provider.
979 error = SNINativeMethodWrapper.SNIAddProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.SSL_PROV, ref info);
981 if (error != TdsEnums.SNI_SUCCESS) {
982 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
983 ThrowExceptionAndWarning(_physicalStateObj);
986 // in the case where an async connection is made, encryption is used and Windows Authentication is used,
987 // wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its
988 // Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
989 // before calling SNISecGenClientContext).
990 error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining());
991 if (error != TdsEnums.SNI_SUCCESS)
993 _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
994 ThrowExceptionAndWarning(_physicalStateObj);
997 // create a new packet encryption changes the internal packet size Bug# 228403
998 try {} // EmptyTry/Finally to avoid FXCop violation
999 finally {
1000 _physicalStateObj.ClearAllWritePackets();
1004 break;
1006 case (int)PreLoginOptions.INSTANCE:
1007 payloadOffset = payload[offset++] << 8 | payload[offset++];
1008 payloadLength = payload[offset++] << 8 | payload[offset++];
1010 byte ERROR_INST = 0x1;
1011 byte instanceResult = payload[payloadOffset];
1013 if (instanceResult == ERROR_INST) {
1014 // Check if server says ERROR_INST. That either means the cached info
1015 // we used to connect is not valid or we connected to a named instance
1016 // listening on default params.
1017 return PreLoginHandshakeStatus.InstanceFailure;
1020 break;
1022 case (int)PreLoginOptions.THREADID:
1023 // DO NOTHING FOR THREADID
1024 offset += 4;
1025 break;
1027 case (int)PreLoginOptions.MARS:
1028 payloadOffset = payload[offset++] << 8 | payload[offset++];
1029 payloadLength = payload[offset++] << 8 | payload[offset++];
1031 marsCapable = (payload[payloadOffset] == 0 ? false : true);
1033 Debug.Assert(payload[payloadOffset] == 0 || payload[payloadOffset] == 1, "Value for Mars PreLoginHandshake option not equal to 1 or 0!");
1034 break;
1036 case (int)PreLoginOptions.TRACEID:
1037 // DO NOTHING FOR TRACEID
1038 offset += 4;
1039 break;
1041 case (int)PreLoginOptions.FEDAUTHREQUIRED:
1042 payloadOffset = payload[offset++] << 8 | payload[offset++];
1043 payloadLength = payload[offset++] << 8 | payload[offset++];
1045 // Only 0x00 and 0x01 are accepted values from the server.
1046 if (payload[payloadOffset] != 0x00 && payload[payloadOffset] != 0x01) {
1047 Bid.Trace("<sc.TdsParser.ConsumePreLoginHandshake|ERR> %d#, Server sent an unexpected value for FedAuthRequired PreLogin Option. Value was %d.\n", ObjectID, (int)payload[payloadOffset]);
1048 throw SQL.ParsingErrorValue(ParsingErrorState.FedAuthRequiredPreLoginResponseInvalidValue, (int)payload[payloadOffset]);
1051 // We must NOT use the response for the FEDAUTHREQUIRED PreLogin option, if the connection string option
1052 // was not using the new Authentication keyword or in other words, if Authentication=NotSpecified
1053 // Or AccessToken is not null, mean token based authentication is used.
1054 if ((_connHandler.ConnectionOptions != null
1055 && _connHandler.ConnectionOptions.Authentication != SqlAuthenticationMethod.NotSpecified)
1056 || _connHandler._accessTokenInBytes != null)
1058 fedAuthRequired = payload[payloadOffset] == 0x01 ? true : false;
1060 break;
1062 default:
1063 Debug.Assert(false, "UNKNOWN option in ConsumePreLoginHandshake, option:" + option);
1065 // DO NOTHING FOR THESE UNKNOWN OPTIONS
1066 offset += 4;
1068 break;
1071 if (offset < payload.Length) {
1072 option = payload[offset++];
1074 else {
1075 break;
1079 return PreLoginHandshakeStatus.Successful;
1082 internal void Deactivate(bool connectionIsDoomed) {
1083 // Called when the connection that owns us is deactivated.
1085 if (Bid.AdvancedOn) {
1086 Bid.Trace("<sc.TdsParser.Deactivate|ADV> %d# deactivating\n", ObjectID);
1089 if (Bid.IsOn(Bid.ApiGroup.StateDump)) {
1090 Bid.Trace("<sc.TdsParser.Deactivate|STATE> %d#, %ls\n", ObjectID, TraceString());
1093 if (MARSOn) {
1094 _sessionPool.Deactivate();
1097 Debug.Assert(connectionIsDoomed || null == _pendingTransaction, "pending transaction at disconnect?");
1099 if (!connectionIsDoomed && null != _physicalStateObj) {
1100 if (_physicalStateObj._pendingData) {
1101 DrainData(_physicalStateObj);
1104 if (_physicalStateObj.HasOpenResult) { // SQL BU DT 383773 - need to decrement openResultCount for all pending operations.
1105 _physicalStateObj.DecrementOpenResultCount();
1109 // Any active, non-distributed transaction must be rolled back. We
1110 // need to wait for distributed transactions to be completed by the
1111 // transaction manager -- we don't want to automatically roll them
1112 // back.
1114 // Note that when there is a transaction delegated to this connection,
1115 // we will defer the deactivation of this connection until the
1116 // transaction manager completes the transaction.
1117 SqlInternalTransaction currentTransaction = CurrentTransaction;
1119 if (null != currentTransaction && currentTransaction.HasParentTransaction) {
1120 currentTransaction.CloseFromConnection();
1121 Debug.Assert(null == CurrentTransaction, "rollback didn't clear current transaction?");
1124 Statistics = null; // must come after CleanWire or we won't count the stuff that happens there...
1127 // Used to close the connection and then free the memory allocated for the netlib connection.
1128 internal void Disconnect() {
1129 if (null != _sessionPool) {
1130 // MARSOn may be true, but _sessionPool not yet created
1131 _sessionPool.Dispose();
1134 // Can close the connection if its open or broken
1135 if (_state != TdsParserState.Closed) {
1136 //benign assert - the user could close the connection before consuming all the data
1137 //Debug.Assert(_physicalStateObj._inBytesUsed == _physicalStateObj._inBytesRead && _physicalStateObj._outBytesUsed == _physicalStateObj._inputHeaderLen, "TDSParser closed with data not fully sent or consumed.");
1139 _state = TdsParserState.Closed;
1141 try {
1142 // If the _physicalStateObj has an owner, we will delay the disposal until the owner is finished with it
1143 if (!_physicalStateObj.HasOwner) {
1144 _physicalStateObj.SniContext = SniContext.Snix_Close;
1145 #if DEBUG
1146 _physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
1147 #endif
1148 _physicalStateObj.Dispose();
1150 else {
1151 // Remove the "initial" callback (this will allow the stateObj to be GC collected if need be)
1152 _physicalStateObj.DecrementPendingCallbacks(false);
1155 // Not allocated until MARS is actually enabled in SNI.
1156 if (null != _pMarsPhysicalConObj) {
1157 _pMarsPhysicalConObj.Dispose();
1160 finally {
1161 _pMarsPhysicalConObj = null;
1166 // Fires a single InfoMessageEvent
1167 private void FireInfoMessageEvent(SqlConnection connection, TdsParserStateObject stateObj, SqlError error) {
1169 string serverVersion = null;
1171 Debug.Assert(connection != null && _connHandler.Connection == connection);
1173 if (_state == TdsParserState.OpenLoggedIn) {
1174 serverVersion = _connHandler.ServerVersion;
1177 SqlErrorCollection sqlErs = new SqlErrorCollection();
1179 sqlErs.Add(error);
1181 SqlException exc = SqlException.CreateException(sqlErs, serverVersion, _connHandler);
1183 bool notified;
1184 connection.OnInfoMessage(new SqlInfoMessageEventArgs(exc), out notified);
1185 if (notified) {
1186 // observable side-effects, no retry
1187 stateObj._syncOverAsync = true;
1189 return;
1192 internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) {
1193 Debug.Assert(_currentTransaction != null && _currentTransaction == internalTransaction, "disconnecting different transaction");
1195 if (_currentTransaction != null && _currentTransaction == internalTransaction) {
1196 _currentTransaction = null;
1200 internal void RollbackOrphanedAPITransactions() {
1201 // Any active, non-distributed transaction must be rolled back.
1202 SqlInternalTransaction currentTransaction = CurrentTransaction;
1204 if (null != currentTransaction && currentTransaction.HasParentTransaction && currentTransaction.IsOrphaned) {
1205 currentTransaction.CloseFromConnection();
1206 Debug.Assert(null == CurrentTransaction, "rollback didn't clear current transaction?");
1210 internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, bool callerHasConnectionLock = false, bool asyncClose = false) {
1211 Debug.Assert(!callerHasConnectionLock || _connHandler._parserLock.ThreadMayHaveLock(), "Caller claims to have lock, but connection lock is not taken");
1213 SqlException exception = null;
1214 bool breakConnection;
1216 // This function should only be called when there was an error or warning. If there aren't any
1217 // errors, the handler will be called for the warning(s). If there was an error, the warning(s) will
1218 // be copied to the end of the error collection so that the user may see all the errors and also the
1219 // warnings that occurred.
1220 // can be deleted)
1221 SqlErrorCollection temp = stateObj.GetFullErrorAndWarningCollection(out breakConnection);
1223 Debug.Assert(temp.Count > 0, "TdsParser::ThrowExceptionAndWarning called with no exceptions or warnings!");
1224 if (temp.Count == 0)
1226 Bid.Trace("<sc.TdsParser.ThrowExceptionAndWarning|ERR> Potential multi-threaded misuse of connection, unexpectedly empty warnings/errors under lock %d#\n", ObjectID);
1228 Debug.Assert(_connHandler != null, "TdsParser::ThrowExceptionAndWarning called with null connectionHandler!");
1230 // Don't break the connection if it is already closed
1231 breakConnection &= (TdsParserState.Closed != _state);
1232 if (breakConnection) {
1233 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))) {
1234 // 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'
1235 // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/459546
1236 // For Multisubnet Failover we slice the timeout to make reconnecting faster (with the assumption that the server will not failover instantaneously)
1237 // 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
1238 breakConnection = false;
1239 Disconnect();
1241 else {
1242 _state = TdsParserState.Broken;
1246 Debug.Assert(temp != null, "TdsParser::ThrowExceptionAndWarning: 0 errors in collection");
1247 if (temp != null && temp.Count > 0) {
1248 // Construct the exception now that we've collected all the errors
1249 string serverVersion = null;
1250 if (_state == TdsParserState.OpenLoggedIn) {
1251 serverVersion = _connHandler.ServerVersion;
1253 exception = SqlException.CreateException(temp, serverVersion, _connHandler);
1254 if (exception.Procedure == TdsEnums.INIT_ADAL_PACKAGE || exception.Procedure == TdsEnums.INIT_SSPI_PACKAGE) {
1255 exception._doNotReconnect = true;
1259 // call OnError outside of _ErrorCollectionLock to avoid deadlock
1260 if (exception != null) {
1261 if (breakConnection) {
1262 // report exception to pending async operation
1263 // before OnConnectionClosed overrides the exception
1264 // due to connection close notification through references
1265 var taskSource = stateObj._networkPacketTaskSource;
1266 if (taskSource != null) {
1267 taskSource.TrySetException(ADP.ExceptionWithStackTrace(exception));
1271 if (asyncClose) {
1272 // Wait until we have the parser lock, then try to close
1273 var connHandler = _connHandler;
1274 Action<Action> wrapCloseAction = closeAction => {
1275 Task.Factory.StartNew(() => {
1276 connHandler._parserLock.Wait(canReleaseFromAnyThread: false);
1277 connHandler.ThreadHasParserLockForClose = true;
1278 try {
1279 closeAction();
1281 finally {
1282 connHandler.ThreadHasParserLockForClose = false;
1283 connHandler._parserLock.Release();
1288 _connHandler.OnError(exception, breakConnection, wrapCloseAction);
1290 else {
1291 // Let close know that we already have the _parserLock
1292 bool threadAlreadyHadParserLockForClose = _connHandler.ThreadHasParserLockForClose;
1293 if (callerHasConnectionLock) {
1294 _connHandler.ThreadHasParserLockForClose = true;
1296 try {
1297 // the following handler will throw an exception or generate a warning event
1298 _connHandler.OnError(exception, breakConnection);
1300 finally {
1301 if (callerHasConnectionLock) {
1302 _connHandler.ThreadHasParserLockForClose = threadAlreadyHadParserLockForClose;
1309 internal SqlError ProcessSNIError(TdsParserStateObject stateObj) {
1310 #if DEBUG
1311 // There is an exception here for MARS as its possible that another thread has closed the connection just as we see an error
1312 Debug.Assert(SniContext.Undefined!=stateObj.DebugOnlyCopyOfSniContext || ((_fMARS) && ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken))), "SniContext must not be None");
1313 #endif
1314 SNINativeMethodWrapper.SNI_Error sniError = new SNINativeMethodWrapper.SNI_Error();
1315 SNINativeMethodWrapper.SNIGetLastError(sniError);
1317 if (sniError.sniError != 0) {
1319 // handle special SNI error codes that are converted into exception which is not a SqlException.
1320 switch (sniError.sniError) {
1321 case (int)SNINativeMethodWrapper.SniSpecialErrors.MultiSubnetFailoverWithMoreThan64IPs:
1322 // Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported.
1323 throw SQL.MultiSubnetFailoverWithMoreThan64IPs();
1325 case (int)SNINativeMethodWrapper.SniSpecialErrors.MultiSubnetFailoverWithInstanceSpecified:
1326 // Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported.
1327 throw SQL.MultiSubnetFailoverWithInstanceSpecified();
1329 case (int)SNINativeMethodWrapper.SniSpecialErrors.MultiSubnetFailoverWithNonTcpProtocol:
1330 // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol.
1331 throw SQL.MultiSubnetFailoverWithNonTcpProtocol();
1333 // continue building SqlError instance
1337 // error.errorMessage is null terminated with garbage beyond that, since fixed length
1338 string errorMessage;
1339 int MessageLength = Array.IndexOf(sniError.errorMessage, '\0');
1340 if (MessageLength == -1) {
1341 errorMessage = String.Empty; // If we don't see the expected null return nothing.
1342 } else {
1343 errorMessage = new String(sniError.errorMessage, 0, MessageLength);
1346 // Format SNI errors and add Context Information
1348 // General syntax is:
1349 // <sqlclient message>
1350 // (provider:<SNIx provider>, error: <SNIx error code> - <SNIx error message>)
1352 // errorMessage | sniError |
1353 // -------------------------------------------
1354 // ==null | x | must never happen
1355 // !=null | != 0 | retrieve corresponding errorMessage from resources
1356 // !=null | == 0 | replace text left of errorMessage
1359 Debug.Assert(!ADP.IsEmpty(errorMessage),"Empty error message received from SNI");
1361 string sqlContextInfo = Res.GetString(Enum.GetName(typeof(SniContext), stateObj.SniContext));
1363 string providerRid = String.Format((IFormatProvider)null,"SNI_PN{0}", (int)sniError.provider);
1364 string providerName = Res.GetString(providerRid);
1365 Debug.Assert(!ADP.IsEmpty(providerName), String.Format((IFormatProvider)null,"invalid providerResourceId '{0}'", providerRid));
1366 uint win32ErrorCode = sniError.nativeError;
1368 if (sniError.sniError == 0) {
1369 // Provider error. The message from provider is preceeded with non-localizable info from SNI
1370 // strip provider info from SNI
1372 int iColon = errorMessage.IndexOf(':');
1373 Debug.Assert(0<=iColon, "':' character missing in sni errorMessage");
1374 Debug.Assert(errorMessage.Length>iColon+1 && errorMessage[iColon+1]==' ', "Expecting a space after the ':' character");
1376 // extract the message excluding the colon and trailing cr/lf chars
1377 if (0<=iColon) {
1378 int len = errorMessage.Length;
1379 len -=2; // exclude "\r\n" sequence
1380 iColon+=2; // skip over ": " sequence
1381 len-=iColon;
1383 The error message should come back in the following format: "TCP Provider: MESSAGE TEXT"
1384 Fix Bug 370686, if the message is recieved on a Win9x OS, the error message will not contain MESSAGE TEXT
1385 per Bug: 269574. If we get a errormessage with no message text, just return the entire message otherwise
1386 return just the message text.
1388 if (len > 0) {
1389 errorMessage = errorMessage.Substring(iColon, len);
1393 else {
1394 // SNI error. Replace the entire message
1396 errorMessage = SQL.GetSNIErrorMessage((int)sniError.sniError);
1398 // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
1399 if (sniError.sniError == (int)SNINativeMethodWrapper.SniSpecialErrors.LocalDBErrorCode) {
1400 errorMessage += LocalDBAPI.GetLocalDBMessage((int)sniError.nativeError);
1401 win32ErrorCode = 0;
1404 errorMessage = String.Format((IFormatProvider)null, "{0} (provider: {1}, error: {2} - {3})",
1405 sqlContextInfo, providerName, (int)sniError.sniError, errorMessage);
1407 return new SqlError((int)sniError.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS,
1408 _server, errorMessage, sniError.function, (int)sniError.lineNumber, win32ErrorCode);
1411 internal void CheckResetConnection(TdsParserStateObject stateObj) {
1412 if (_fResetConnection && !stateObj._fResetConnectionSent) {
1413 Debug.Assert(stateObj._outputPacketNumber == 1 || stateObj._outputPacketNumber == 2, "In ResetConnection logic unexpectedly!");
1414 try {
1415 if (_fMARS && !stateObj._fResetEventOwned) {
1416 // If using Async & MARS and we do not own ResetEvent - grab it. We need to not grab lock here
1417 // for case where multiple packets are sent to server from one execute.
1418 stateObj._fResetEventOwned = _resetConnectionEvent.WaitOne(stateObj.GetTimeoutRemaining(), false);
1420 if (stateObj._fResetEventOwned) {
1421 if (stateObj.TimeoutHasExpired) {
1422 // We didn't timeout on the WaitOne, but we timed out by the time we decremented stateObj._timeRemaining.
1423 stateObj._fResetEventOwned = !_resetConnectionEvent.Set();
1424 stateObj.TimeoutTime = 0;
1428 if (!stateObj._fResetEventOwned) {
1429 // We timed out waiting for ResetEvent. Throw timeout exception and reset
1430 // the buffer. Nothing else to do since we did not actually send anything
1431 // to the server.
1432 stateObj.ResetBuffer();
1433 Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point.");
1434 stateObj.AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, (byte)0x00, TdsEnums.MIN_ERROR_CLASS, _server, _connHandler.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT));
1435 Debug.Assert(_connHandler._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock");
1436 ThrowExceptionAndWarning(stateObj, callerHasConnectionLock: true);
1440 if (_fResetConnection) {
1441 // Check again to see if we need to send reset.
1443 Debug.Assert(!stateObj._fResetConnectionSent, "Unexpected state for sending reset connection");
1444 Debug.Assert(_isShiloh, "TdsParser.cs: Error! _fResetConnection true when not going against Shiloh or later!");
1446 if (_fPreserveTransaction) {
1447 // if we are reseting, set bit in header by or'ing with other value
1448 stateObj._outBuff[1] = (Byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION_PRESERVE_TRANSACTION);
1450 else {
1451 // if we are reseting, set bit in header by or'ing with other value
1452 stateObj._outBuff[1] = (Byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION);
1455 if (!_fMARS) {
1456 _fResetConnection = false; // If not MARS, can turn off flag now.
1457 _fPreserveTransaction = false;
1459 else {
1460 stateObj._fResetConnectionSent = true; // Otherwise set flag so we don't resend on multiple packet execute.
1463 else if (_fMARS && stateObj._fResetEventOwned) {
1464 Debug.Assert(!stateObj._fResetConnectionSent, "Unexpected state on WritePacket ResetConnection");
1466 // Otherwise if Yukon and we grabbed the event, free it. Another execute grabbed the event and
1467 // took care of sending the reset.
1468 stateObj._fResetEventOwned = !_resetConnectionEvent.Set();
1469 Debug.Assert(!stateObj._fResetEventOwned, "Invalid AutoResetEvent state!");
1472 catch (Exception) {
1473 if (_fMARS && stateObj._fResetEventOwned) {
1474 // If exception thrown, and we are on Yukon and own the event, release it!
1475 stateObj._fResetConnectionSent = false;
1476 stateObj._fResetEventOwned = !_resetConnectionEvent.Set();
1477 Debug.Assert(!stateObj._fResetEventOwned, "Invalid AutoResetEvent state!");
1480 throw;
1483 #if DEBUG
1484 else {
1485 Debug.Assert(!_fResetConnection ||
1486 (_fResetConnection && stateObj._fResetConnectionSent && stateObj._fResetEventOwned),
1487 "Unexpected state on else ResetConnection block in WritePacket");
1489 #endif
1493 // Takes a 16 bit short and writes it.
1495 internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) {
1496 if (null == stateObj._bShortBytes) {
1497 stateObj._bShortBytes = new byte[2];
1499 else {
1500 Debug.Assert(2 == stateObj._bShortBytes.Length);
1503 byte[] bytes = stateObj._bShortBytes;
1504 int current = 0;
1505 bytes[current++] = (byte)(v & 0xff);
1506 bytes[current++] = (byte)((v >> 8) & 0xff);
1507 return bytes;
1510 internal void WriteShort(int v, TdsParserStateObject stateObj) {
1511 ReliabilitySection.Assert("unreliable call to WriteShort"); // you need to setup for a thread abort somewhere before you call this method
1513 if ((stateObj._outBytesUsed + 2) > stateObj._outBuff.Length) {
1514 // if all of the short doesn't fit into the buffer
1515 stateObj.WriteByte((byte)(v & 0xff));
1516 stateObj.WriteByte((byte)((v >> 8) & 0xff));
1518 else {
1519 // all of the short fits into the buffer
1520 stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff);
1521 stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff);
1522 stateObj._outBytesUsed += 2;
1526 internal void WriteUnsignedShort(ushort us, TdsParserStateObject stateObj) {
1527 WriteShort((short)us, stateObj);
1531 // Takes a long and writes out an unsigned int
1533 internal byte[] SerializeUnsignedInt(uint i, TdsParserStateObject stateObj) {
1534 return SerializeInt((int)i, stateObj);
1537 internal void WriteUnsignedInt(uint i, TdsParserStateObject stateObj) {
1538 WriteInt((int)i, stateObj);
1542 // Takes an int and writes it as an int.
1544 internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) {
1545 if (null == stateObj._bIntBytes) {
1546 stateObj._bIntBytes = new byte[4];
1548 else {
1549 Debug.Assert (4 == stateObj._bIntBytes.Length);
1552 int current = 0;
1553 byte[] bytes = stateObj._bIntBytes;
1554 bytes[current++] = (byte)(v & 0xff);
1555 bytes[current++] = (byte)((v >> 8) & 0xff);
1556 bytes[current++] = (byte)((v >> 16) & 0xff);
1557 bytes[current++] = (byte)((v >> 24) & 0xff);
1558 return bytes;
1561 internal void WriteInt(int v, TdsParserStateObject stateObj) {
1562 ReliabilitySection.Assert("unreliable call to WriteInt"); // you need to setup for a thread abort somewhere before you call this method
1564 if ((stateObj._outBytesUsed + 4) > stateObj._outBuff.Length) {
1565 // if all of the int doesn't fit into the buffer
1566 for (int shiftValue = 0; shiftValue < sizeof(int) * 8; shiftValue += 8) {
1567 stateObj.WriteByte((byte)((v >> shiftValue) & 0xff));
1570 else {
1571 // all of the int fits into the buffer
1572 // NOTE: We don't use a loop here for performance
1573 stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff);
1574 stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff);
1575 stateObj._outBuff[stateObj._outBytesUsed + 2] = (byte)((v >> 16) & 0xff);
1576 stateObj._outBuff[stateObj._outBytesUsed + 3] = (byte)((v >> 24) & 0xff);
1577 stateObj._outBytesUsed += 4;
1582 // Takes a float and writes it as a 32 bit float.
1584 internal byte[] SerializeFloat(float v) {
1585 if (Single.IsInfinity(v) || Single.IsNaN(v)) {
1586 throw ADP.ParameterValueOutOfRange(v.ToString());
1589 return BitConverter.GetBytes(v);
1592 internal void WriteFloat(float v, TdsParserStateObject stateObj) {
1593 byte[] bytes = BitConverter.GetBytes(v);
1595 stateObj.WriteByteArray(bytes, bytes.Length, 0);
1599 // Takes a long and writes it as a long.
1601 internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) {
1602 int current = 0;
1603 if (null == stateObj._bLongBytes) {
1604 stateObj._bLongBytes = new byte[8];
1607 byte[] bytes = stateObj._bLongBytes;
1608 Debug.Assert (8 == bytes.Length, "Cached buffer has wrong size");
1610 bytes[current++] = (byte)(v & 0xff);
1611 bytes[current++] = (byte)((v >> 8) & 0xff);
1612 bytes[current++] = (byte)((v >> 16) & 0xff);
1613 bytes[current++] = (byte)((v >> 24) & 0xff);
1614 bytes[current++] = (byte)((v >> 32) & 0xff);
1615 bytes[current++] = (byte)((v >> 40) & 0xff);
1616 bytes[current++] = (byte)((v >> 48) & 0xff);
1617 bytes[current++] = (byte)((v >> 56) & 0xff);
1619 return bytes;
1622 internal void WriteLong(long v, TdsParserStateObject stateObj) {
1623 ReliabilitySection.Assert("unreliable call to WriteLong"); // you need to setup for a thread abort somewhere before you call this method
1625 if ((stateObj._outBytesUsed + 8) > stateObj._outBuff.Length) {
1626 // if all of the long doesn't fit into the buffer
1627 for (int shiftValue = 0; shiftValue < sizeof(long) * 8; shiftValue += 8) {
1628 stateObj.WriteByte((byte)((v >> shiftValue) & 0xff));
1631 else {
1632 // all of the long fits into the buffer
1633 // NOTE: We don't use a loop here for performance
1634 stateObj._outBuff[stateObj._outBytesUsed] = (byte)(v & 0xff);
1635 stateObj._outBuff[stateObj._outBytesUsed + 1] = (byte)((v >> 8) & 0xff);
1636 stateObj._outBuff[stateObj._outBytesUsed + 2] = (byte)((v >> 16) & 0xff);
1637 stateObj._outBuff[stateObj._outBytesUsed + 3] = (byte)((v >> 24) & 0xff);
1638 stateObj._outBuff[stateObj._outBytesUsed + 4] = (byte)((v >> 32) & 0xff);
1639 stateObj._outBuff[stateObj._outBytesUsed + 5] = (byte)((v >> 40) & 0xff);
1640 stateObj._outBuff[stateObj._outBytesUsed + 6] = (byte)((v >> 48) & 0xff);
1641 stateObj._outBuff[stateObj._outBytesUsed + 7] = (byte)((v >> 56) & 0xff);
1642 stateObj._outBytesUsed += 8;
1647 // Takes a long and writes part of it
1649 internal byte[] SerializePartialLong(long v, int length) {
1650 Debug.Assert(length <= 8, "Length specified is longer than the size of a long");
1651 Debug.Assert(length >= 0, "Length should not be negative");
1653 byte[] bytes = new byte[length];
1655 // all of the long fits into the buffer
1656 for (int index = 0; index < length; index++) {
1657 bytes[index] = (byte)((v >> (index * 8)) & 0xff);
1660 return bytes;
1663 internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj) {
1664 ReliabilitySection.Assert("unreliable call to WritePartialLong"); // you need to setup for a thread abort somewhere before you call this method
1665 Debug.Assert(length <= 8, "Length specified is longer than the size of a long");
1666 Debug.Assert(length >= 0, "Length should not be negative");
1668 if ((stateObj._outBytesUsed + length) > stateObj._outBuff.Length) {
1669 // if all of the long doesn't fit into the buffer
1670 for (int shiftValue = 0; shiftValue < length * 8; shiftValue += 8) {
1671 stateObj.WriteByte((byte)((v >> shiftValue) & 0xff));
1674 else {
1675 // all of the long fits into the buffer
1676 for (int index = 0; index < length; index++) {
1677 stateObj._outBuff[stateObj._outBytesUsed + index] = (byte)((v >> (index * 8)) & 0xff);
1679 stateObj._outBytesUsed += length;
1684 // Takes a ulong and writes it as a ulong.
1686 internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) {
1687 WriteLong((long)uv, stateObj);
1691 // Takes a double and writes it as a 64 bit double.
1693 internal byte[] SerializeDouble(double v) {
1694 if (Double.IsInfinity(v) || Double.IsNaN(v)) {
1695 throw ADP.ParameterValueOutOfRange(v.ToString());
1698 return BitConverter.GetBytes(v);
1701 internal void WriteDouble(double v, TdsParserStateObject stateObj) {
1702 byte[] bytes = BitConverter.GetBytes(v);
1704 stateObj.WriteByteArray(bytes, bytes.Length, 0);
1707 internal void PrepareResetConnection(bool preserveTransaction) {
1708 // Set flag to reset connection upon next use - only for use on shiloh!
1709 _fResetConnection = true;
1710 _fPreserveTransaction = preserveTransaction;
1713 internal bool RunReliably(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) {
1714 RuntimeHelpers.PrepareConstrainedRegions();
1715 try {
1716 #if DEBUG
1717 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
1718 RuntimeHelpers.PrepareConstrainedRegions();
1719 try {
1720 tdsReliabilitySection.Start();
1721 #endif //DEBUG
1722 return Run(runBehavior, cmdHandler, dataStream, bulkCopyHandler, stateObj);
1723 #if DEBUG
1725 finally {
1726 tdsReliabilitySection.Stop();
1728 #endif //DEBUG
1730 catch (OutOfMemoryException) {
1731 _connHandler.DoomThisConnection();
1732 throw;
1734 catch (StackOverflowException) {
1735 _connHandler.DoomThisConnection();
1736 throw;
1738 catch (ThreadAbortException) {
1739 _connHandler.DoomThisConnection();
1740 throw;
1745 internal bool Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) {
1746 bool syncOverAsync = stateObj._syncOverAsync;
1749 stateObj._syncOverAsync = true;
1751 bool dataReady;
1752 bool result = TryRun(runBehavior, cmdHandler, dataStream, bulkCopyHandler, stateObj, out dataReady);
1753 Debug.Assert(result == true, "Should never return false when _syncOverAsync is set");
1754 return dataReady;
1756 finally
1758 stateObj._syncOverAsync = syncOverAsync;
1762 /// <summary>
1763 /// Checks if the given token is a valid TDS token
1764 /// </summary>
1765 /// <param name="token">Token to check</param>
1766 /// <returns>True if the token is a valid TDS token, otherwise false</returns>
1767 internal static bool IsValidTdsToken(byte token) {
1768 return (
1769 token == TdsEnums.SQLERROR ||
1770 token == TdsEnums.SQLINFO ||
1771 token == TdsEnums.SQLLOGINACK ||
1772 token == TdsEnums.SQLENVCHANGE ||
1773 token == TdsEnums.SQLRETURNVALUE ||
1774 token == TdsEnums.SQLRETURNSTATUS ||
1775 token == TdsEnums.SQLCOLNAME ||
1776 token == TdsEnums.SQLCOLFMT ||
1777 token == TdsEnums.SQLCOLMETADATA ||
1778 token == TdsEnums.SQLALTMETADATA ||
1779 token == TdsEnums.SQLTABNAME ||
1780 token == TdsEnums.SQLCOLINFO ||
1781 token == TdsEnums.SQLORDER ||
1782 token == TdsEnums.SQLALTROW ||
1783 token == TdsEnums.SQLROW ||
1784 token == TdsEnums.SQLNBCROW ||
1785 token == TdsEnums.SQLDONE ||
1786 token == TdsEnums.SQLDONEPROC ||
1787 token == TdsEnums.SQLDONEINPROC ||
1788 token == TdsEnums.SQLROWCRC ||
1789 token == TdsEnums.SQLSECLEVEL ||
1790 token == TdsEnums.SQLPROCID ||
1791 token == TdsEnums.SQLOFFSET ||
1792 token == TdsEnums.SQLSSPI ||
1793 token == TdsEnums.SQLFEATUREEXTACK ||
1794 token == TdsEnums.SQLSESSIONSTATE ||
1795 token == TdsEnums.SQLFEDAUTHINFO);
1798 // Main parse loop for the top-level tds tokens, calls back into the I*Handler interfaces
1799 internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, out bool dataReady) {
1800 ReliabilitySection.Assert("unreliable call to Run"); // you need to setup for a thread abort somewhere before you call this method
1801 Debug.Assert((SniContext.Undefined != stateObj.SniContext) && // SniContext must not be Undefined
1802 ((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)
1803 String.Format("Unexpected SniContext on call to TryRun; SniContext={0}", stateObj.SniContext));
1805 if (TdsParserState.Broken == State || TdsParserState.Closed == State ) {
1806 dataReady = true;
1807 return true; // Just in case this is called in a loop, expecting data to be returned.
1810 dataReady = false;
1812 do {
1813 // If there is data ready, but we didn't exit the loop, then something is wrong
1814 Debug.Assert(!dataReady, "dataReady not expected - did we forget to skip the row?");
1816 if (stateObj._internalTimeout) {
1817 runBehavior = RunBehavior.Attention;
1820 if (TdsParserState.Broken == State || TdsParserState.Closed == State)
1821 break; // jump out of the loop if the state is already broken or closed.
1823 if (!stateObj._accumulateInfoEvents && (stateObj._pendingInfoEvents != null)) {
1824 if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) {
1825 SqlConnection connection = null;
1826 if (_connHandler != null)
1827 connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref
1828 // We are omitting checks for error.Class in the code below (see processing of INFO) since we know (and assert) that error class
1829 // error.Class < TdsEnums.MIN_ERROR_CLASS for info message.
1830 // Also we know that TdsEnums.MIN_ERROR_CLASS<TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS
1831 if ((connection != null) && connection.FireInfoMessageEventOnUserErrors)
1833 foreach (SqlError error in stateObj._pendingInfoEvents)
1834 FireInfoMessageEvent(connection, stateObj, error);
1836 else
1837 foreach (SqlError error in stateObj._pendingInfoEvents)
1838 stateObj.AddWarning(error);
1841 stateObj._pendingInfoEvents=null;
1844 byte token;
1845 if (!stateObj.TryReadByte(out token)) {
1846 return false;
1849 if (!IsValidTdsToken(token)) {
1850 Debug.Assert(false, String.Format((IFormatProvider)null, "unexpected token; token = {0,-2:X2}", token));
1851 _state = TdsParserState.Broken;
1852 _connHandler.BreakConnection();
1853 Bid.Trace("<sc.TdsParser.Run|ERR> Potential multi-threaded misuse of connection, unexpected TDS token found %d#\n", ObjectID);
1854 throw SQL.ParsingErrorToken(ParsingErrorState.InvalidTdsTokenReceived, token); // MDAC 82443
1857 int tokenLength;
1858 if (!TryGetTokenLength(token, stateObj, out tokenLength)) {
1859 return false;
1862 switch (token) {
1863 case TdsEnums.SQLERROR:
1864 case TdsEnums.SQLINFO:
1866 if (token == TdsEnums.SQLERROR) {
1867 stateObj._errorTokenReceived = true; // Keep track of the fact error token was received - for Done processing.
1870 SqlError error;
1871 if (!TryProcessError(token, stateObj, out error)) {
1872 return false;
1875 if (token == TdsEnums.SQLINFO && stateObj._accumulateInfoEvents)
1877 Debug.Assert(error.Class < TdsEnums.MIN_ERROR_CLASS, "INFO with class > TdsEnums.MIN_ERROR_CLASS");
1879 if (stateObj._pendingInfoEvents == null)
1880 stateObj._pendingInfoEvents = new List<SqlError>();
1881 stateObj._pendingInfoEvents.Add(error);
1882 stateObj._syncOverAsync = true;
1883 break;
1886 if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) {
1887 // If FireInfoMessageEventOnUserErrors is true, we have to fire event without waiting.
1888 // Otherwise we can go ahead and add it to errors/warnings collection.
1889 SqlConnection connection = null;
1890 if (_connHandler != null)
1891 connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref
1893 if ((connection != null) &&
1894 (connection.FireInfoMessageEventOnUserErrors == true) &&
1895 (error.Class <= TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS)) {
1896 // Fire SqlInfoMessage here
1897 FireInfoMessageEvent(connection, stateObj, error);
1899 else {
1900 // insert error/info into the appropriate exception - warning if info, exception if error
1901 if (error.Class < TdsEnums.MIN_ERROR_CLASS) {
1902 stateObj.AddWarning(error);
1904 else if (error.Class < TdsEnums.FATAL_ERROR_CLASS) {
1905 // VSTFDEVDIV 479643: continue results processing for all non-fatal errors (<20)
1907 stateObj.AddError(error);
1909 // Add it to collection - but do NOT change run behavior UNLESS
1910 // we are in an ExecuteReader call - at which time we will be throwing
1911 // anyways so we need to consume all errors. This is not the case
1912 // if we have already given out a reader. If we have already given out
1913 // a reader we need to throw the error but not halt further processing. We used to
1914 // halt processing and that was a bug preventing the user from
1915 // processing subsequent results.
1917 if (null != dataStream) { // Webdata 104560
1918 if (!dataStream.IsInitialized) {
1919 runBehavior = RunBehavior.UntilDone;
1923 else {
1924 stateObj.AddError(error);
1926 // Else we have a fatal error and we need to change the behavior
1927 // since we want the complete error information in the exception.
1928 // Besides - no further results will be received.
1929 runBehavior = RunBehavior.UntilDone;
1933 else if (error.Class >= TdsEnums.FATAL_ERROR_CLASS) {
1934 stateObj.AddError(error);
1936 break;
1939 case TdsEnums.SQLCOLINFO:
1941 if (null != dataStream) {
1942 _SqlMetaDataSet metaDataSet;
1943 if (!TryProcessColInfo(dataStream.MetaData, dataStream, stateObj, out metaDataSet)) {
1944 return false;
1946 if (!dataStream.TrySetMetaData(metaDataSet, false)) {
1947 return false;
1949 dataStream.BrowseModeInfoConsumed = true;
1951 else { // no dataStream
1952 if (!stateObj.TrySkipBytes(tokenLength)) {
1953 return false;
1956 break;
1959 case TdsEnums.SQLDONE:
1960 case TdsEnums.SQLDONEPROC:
1961 case TdsEnums.SQLDONEINPROC:
1963 // RunBehavior can be modified - see SQL BU DT 269516 & 290090
1964 if (!TryProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj)) {
1965 return false;
1967 if ((token == TdsEnums.SQLDONEPROC) && (cmdHandler != null)) {
1968 // If the current parse/read is for the results of describe parameter encryption RPC requests,
1969 // call a different handler which will update the describe parameter encryption RPC structures
1970 // with the results, instead of the actual user RPC requests.
1971 if (cmdHandler.IsDescribeParameterEncryptionRPCCurrentlyInProgress) {
1972 cmdHandler.OnDoneDescribeParameterEncryptionProc(stateObj);
1974 else {
1975 cmdHandler.OnDoneProc();
1979 break;
1982 case TdsEnums.SQLORDER:
1984 // don't do anything with the order token so read off the pipe
1985 if (!stateObj.TrySkipBytes(tokenLength)) {
1986 return false;
1988 break;
1991 case TdsEnums.SQLALTMETADATA:
1993 stateObj.CloneCleanupAltMetaDataSetArray();
1995 if (stateObj._cleanupAltMetaDataSetArray == null) {
1996 // create object on demand (lazy creation)
1997 stateObj._cleanupAltMetaDataSetArray = new _SqlMetaDataSetCollection();
2000 _SqlMetaDataSet cleanupAltMetaDataSet;
2001 if (!TryProcessAltMetaData(tokenLength, stateObj, out cleanupAltMetaDataSet)) {
2002 return false;
2005 stateObj._cleanupAltMetaDataSetArray.SetAltMetaData(cleanupAltMetaDataSet);
2006 if (null != dataStream) {
2007 byte metadataConsumedByte;
2008 if (!stateObj.TryPeekByte(out metadataConsumedByte)) {
2009 return false;
2011 if (!dataStream.TrySetAltMetaDataSet(cleanupAltMetaDataSet, (TdsEnums.SQLALTMETADATA != metadataConsumedByte))) {
2012 return false;
2016 break;
2019 case TdsEnums.SQLALTROW:
2021 if (!stateObj.TryStartNewRow(isNullCompressed:false)) { // altrows are not currently null compressed
2022 return false;
2025 // read will call run until dataReady. Must not read any data if returnimmetiately set
2026 if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) {
2027 ushort altRowId;
2028 if (!stateObj.TryReadUInt16(out altRowId)) { // get altRowId
2029 return false;
2032 if (!TrySkipRow(stateObj._cleanupAltMetaDataSetArray.GetAltMetaData(altRowId), stateObj)) { // skip altRow
2033 return false;
2036 else {
2037 dataReady = true;
2040 break;
2043 case TdsEnums.SQLENVCHANGE:
2045 // ENVCHANGE must be processed synchronously (since it can modify the state of many objects)
2046 stateObj._syncOverAsync = true;
2048 SqlEnvChange[] env;
2049 if (!TryProcessEnvChange(tokenLength, stateObj, out env)) {
2050 return false;
2053 for (int ii = 0; ii < env.Length; ii++) {
2054 if (env[ii] != null && !this.Connection.IgnoreEnvChange) {
2055 switch (env[ii].type) {
2056 case TdsEnums.ENV_BEGINTRAN:
2057 case TdsEnums.ENV_ENLISTDTC:
2058 // When we get notification from the server of a new
2059 // transaction, we move any pending transaction over to
2060 // the current transaction, then we store the token in it.
2061 // if there isn't a pending transaction, then it's either
2062 // a TSQL transaction or a distributed transaction.
2063 Debug.Assert(null == _currentTransaction, "non-null current transaction with an ENV Change");
2064 _currentTransaction = _pendingTransaction;
2065 _pendingTransaction = null;
2067 if (null != _currentTransaction) {
2068 _currentTransaction.TransactionId = env[ii].newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec.
2070 else {
2071 TransactionType transactionType = (TdsEnums.ENV_BEGINTRAN == env[ii].type) ? TransactionType.LocalFromTSQL : TransactionType.Distributed;
2072 _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env[ii].newLongValue);
2074 if (null != _statistics && !_statisticsIsInTransaction) {
2075 _statistics.SafeIncrement(ref _statistics._transactions);
2077 _statisticsIsInTransaction = true;
2078 _retainedTransactionId = SqlInternalTransaction.NullTransactionId;
2079 break;
2080 case TdsEnums.ENV_DEFECTDTC:
2081 case TdsEnums.ENV_TRANSACTIONENDED:
2082 case TdsEnums.ENV_COMMITTRAN:
2083 // SQLHOT 483
2084 // Must clear the retain id if the server-side transaction ends by anything other
2085 // than rollback.
2086 _retainedTransactionId = SqlInternalTransaction.NullTransactionId;
2087 goto case TdsEnums.ENV_ROLLBACKTRAN;
2088 case TdsEnums.ENV_ROLLBACKTRAN:
2089 // When we get notification of a completed transaction
2090 // we null out the current transaction.
2091 if (null != _currentTransaction) {
2092 #if DEBUG
2093 // Check null for case where Begin and Rollback obtained in the same message.
2094 if (SqlInternalTransaction.NullTransactionId != _currentTransaction.TransactionId) {
2095 Debug.Assert(_currentTransaction.TransactionId != env[ii].newLongValue, "transaction id's are not equal!");
2097 #endif
2099 if (TdsEnums.ENV_COMMITTRAN == env[ii].type) {
2100 _currentTransaction.Completed(TransactionState.Committed);
2102 else if (TdsEnums.ENV_ROLLBACKTRAN == env[ii].type) {
2103 // Hold onto transaction id if distributed tran is rolled back. This must
2104 // be sent to the server on subsequent executions even though the transaction
2105 // is considered to be rolled back.
2106 if (_currentTransaction.IsDistributed && _currentTransaction.IsActive) {
2107 _retainedTransactionId = env[ii].oldLongValue;
2109 _currentTransaction.Completed(TransactionState.Aborted);
2111 else {
2113 _currentTransaction.Completed(TransactionState.Unknown);
2115 _currentTransaction = null;
2117 _statisticsIsInTransaction = false;
2118 break;
2119 default:
2120 _connHandler.OnEnvChange(env[ii]);
2121 break;
2125 break;
2127 case TdsEnums.SQLLOGINACK:
2129 Bid.Trace("<sc.TdsParser.TryRun|SEC> Received login acknowledgement token\n");
2130 SqlLoginAck ack;
2131 if (!TryProcessLoginAck(stateObj, out ack)) {
2132 return false;
2135 _connHandler.OnLoginAck(ack);
2136 break;
2138 case TdsEnums.SQLFEATUREEXTACK:
2140 if (!TryProcessFeatureExtAck(stateObj)) {
2141 return false;
2143 break;
2145 case TdsEnums.SQLFEDAUTHINFO:
2147 _connHandler._federatedAuthenticationInfoReceived = true;
2148 SqlFedAuthInfo info;
2149 Bid.Trace("<sc.TdsParser.TryRun|SEC> Received federated authentication info token\n");
2150 if (!TryProcessFedAuthInfo(stateObj, tokenLength, out info)) {
2151 return false;
2153 _connHandler.OnFedAuthInfo(info);
2154 break;
2156 case TdsEnums.SQLSESSIONSTATE:
2158 if (!TryProcessSessionState(stateObj, tokenLength, _connHandler._currentSessionData)) {
2159 return false;
2161 break;
2163 case TdsEnums.SQLCOLMETADATA:
2165 if (tokenLength != TdsEnums.VARNULL) {
2166 _SqlMetaDataSet metadata;
2167 if (!TryProcessMetaData(tokenLength, stateObj, out metadata,
2168 cmdHandler != null ? cmdHandler.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting)) {
2169 return false;
2171 stateObj._cleanupMetaData = metadata;
2173 else {
2174 if (cmdHandler != null) {
2175 stateObj._cleanupMetaData = cmdHandler.MetaData;
2179 if (null != dataStream) {
2180 byte peekedToken;
2181 if (!stateObj.TryPeekByte(out peekedToken)) { // temporarily cache next byte
2182 return false;
2185 if (!dataStream.TrySetMetaData(stateObj._cleanupMetaData, (TdsEnums.SQLTABNAME == peekedToken || TdsEnums.SQLCOLINFO == peekedToken))) {
2186 return false;
2189 else if (null != bulkCopyHandler) {
2190 bulkCopyHandler.SetMetaData(stateObj._cleanupMetaData);
2192 break;
2194 case TdsEnums.SQLROW:
2195 case TdsEnums.SQLNBCROW:
2197 Debug.Assert(stateObj._cleanupMetaData != null, "Reading a row, but the metadata is null");
2199 if (token == TdsEnums.SQLNBCROW) {
2200 if (!stateObj.TryStartNewRow(isNullCompressed: true, nullBitmapColumnsCount: stateObj._cleanupMetaData.Length)) {
2201 return false;
2204 else {
2205 if (!stateObj.TryStartNewRow(isNullCompressed:false)) {
2206 return false;
2210 if (null != bulkCopyHandler) {
2212 if (!TryProcessRow(stateObj._cleanupMetaData, bulkCopyHandler.CreateRowBuffer(), bulkCopyHandler.CreateIndexMap(), stateObj)) {
2213 return false;
2216 else if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) {
2217 if (!TrySkipRow(stateObj._cleanupMetaData, stateObj)) { // skip rows
2218 return false;
2221 else {
2222 dataReady = true;
2225 if (_statistics != null) {
2226 _statistics.WaitForDoneAfterRow = true;
2228 break;
2230 case TdsEnums.SQLRETURNSTATUS:
2231 int status;
2232 if (!stateObj.TryReadInt32(out status)) {
2233 return false;
2235 if (cmdHandler != null) {
2236 cmdHandler.OnReturnStatus(status);
2238 break;
2239 case TdsEnums.SQLRETURNVALUE:
2241 SqlReturnValue returnValue;
2242 if (!TryProcessReturnValue(tokenLength, stateObj, out returnValue,
2243 cmdHandler != null ? cmdHandler.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting)) {
2244 return false;
2246 if (cmdHandler != null) {
2247 cmdHandler.OnReturnValue(returnValue, stateObj);
2249 break;
2251 case TdsEnums.SQLSSPI:
2253 // token length is length of SSPI data - call ProcessSSPI with it
2255 Debug.Assert(stateObj._syncOverAsync, "ProcessSSPI does not support retry, do not attempt asynchronously");
2256 stateObj._syncOverAsync = true;
2258 ProcessSSPI(tokenLength);
2259 break;
2261 case TdsEnums.SQLTABNAME:
2263 if (null != dataStream) {
2264 MultiPartTableName[] tableNames;
2265 if (!TryProcessTableName(tokenLength, stateObj, out tableNames)) {
2266 return false;
2268 dataStream.TableNames = tableNames;
2270 else {
2271 if (!stateObj.TrySkipBytes(tokenLength)) {
2272 return false;
2275 break;
2278 default:
2279 Debug.Assert(false, "Unhandled token: " + token.ToString(CultureInfo.InvariantCulture));
2280 break;
2283 Debug.Assert(stateObj._pendingData || !dataReady, "dataReady is set, but there is no pending data");
2286 // Loop while data pending & runbehavior not return immediately, OR
2287 // if in attention case, loop while no more pending data & attention has not yet been
2288 // received.
2289 while ((stateObj._pendingData &&
2290 (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))) ||
2291 (!stateObj._pendingData && stateObj._attentionSent && !stateObj._attentionReceived));
2293 #if DEBUG
2294 if ((stateObj._pendingData) && (!dataReady)) {
2295 byte token;
2296 if (!stateObj.TryPeekByte(out token)) {
2297 return false;
2299 Debug.Assert(IsValidTdsToken(token), string.Format("DataReady is false, but next token is not valid: {0,-2:X2}", token));
2301 #endif
2303 if (!stateObj._pendingData) {
2304 if (null != CurrentTransaction) {
2305 CurrentTransaction.Activate();
2309 // if we recieved an attention (but this thread didn't send it) then
2310 // we throw an Operation Cancelled error
2311 if (stateObj._attentionReceived) {
2312 // Dev11 #344723: SqlClient stress hang System_Data!Tcp::ReadSync via a call to SqlDataReader::Close
2313 // Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent
2314 SpinWait.SpinUntil(() => !stateObj._attentionSending);
2316 Debug.Assert(stateObj._attentionSent, "Attention ACK has been received without attention sent");
2317 if (stateObj._attentionSent) {
2318 // Reset attention state.
2319 stateObj._attentionSent = false;
2320 stateObj._attentionReceived = false;
2322 if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj._internalTimeout) {
2323 // Add attention error to collection - if not RunBehavior.Clean!
2324 stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.OperationCancelled(), "", 0));
2329 if (stateObj.HasErrorOrWarning) {
2330 ThrowExceptionAndWarning(stateObj);
2332 return true;
2335 private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange[] sqlEnvChange) {
2336 // There could be multiple environment change messages following this token.
2337 byte byteLength;
2338 int processedLength = 0;
2339 int nvalues = 0;
2340 SqlEnvChange[] envarray = new SqlEnvChange[3]; // Why is this hardcoded to 3?
2342 sqlEnvChange = null;
2344 while (tokenLength > processedLength) {
2346 if (nvalues >= envarray.Length) {
2347 // This is a rare path. Most of the time we will have 1 or 2 envchange data streams.
2348 SqlEnvChange[] newenvarray = new SqlEnvChange[envarray.Length + 3];
2350 for (int ii = 0; ii < envarray.Length; ii++)
2351 newenvarray[ii] = envarray[ii];
2353 envarray = newenvarray;
2356 SqlEnvChange env = new SqlEnvChange();
2358 if (!stateObj.TryReadByte(out env.type)) {
2359 return false;
2362 envarray[nvalues] = env;
2363 nvalues++;
2365 switch (env.type) {
2366 case TdsEnums.ENV_DATABASE:
2367 case TdsEnums.ENV_LANG:
2368 if (!TryReadTwoStringFields(env, stateObj)) {
2369 return false;
2371 break;
2373 case TdsEnums.ENV_CHARSET:
2374 // we copied this behavior directly from luxor - see charset envchange
2375 // section from sqlctokn.c
2376 Debug.Assert(!_isShiloh, "Received ENV_CHARSET on non 7.0 server!");
2377 if (!TryReadTwoStringFields(env, stateObj)) {
2378 return false;
2380 if (env.newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) {
2381 _defaultCodePage = TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE;
2382 _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage);
2384 else {
2385 Debug.Assert(env.newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10");
2387 string stringCodePage = env.newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET);
2389 _defaultCodePage = Int32.Parse(stringCodePage, NumberStyles.Integer, CultureInfo.InvariantCulture);
2390 _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage);
2393 break;
2395 case TdsEnums.ENV_PACKETSIZE:
2396 // take care of packet size right here
2397 Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
2398 if (!TryReadTwoStringFields(env, stateObj)) {
2399 // Changing packet size does not support retry, should not pend"
2400 throw SQL.SynchronousCallMayNotPend();
2402 // Only set on physical state object - this should only occur on LoginAck prior
2403 // to MARS initialization!
2404 Int32 packetSize = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
2406 if (_physicalStateObj.SetPacketSize(packetSize)) {
2407 // If packet size changed, we need to release our SNIPackets since
2408 // those are tied to packet size of connection.
2409 _physicalStateObj.ClearAllWritePackets();
2411 // Update SNI ConsumerInfo value to be resulting packet size
2412 UInt32 unsignedPacketSize = (UInt32) packetSize;
2413 UInt32 result = SNINativeMethodWrapper.SNISetInfo(_physicalStateObj.Handle, SNINativeMethodWrapper.QTypes.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize);
2415 Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo");
2418 break;
2420 case TdsEnums.ENV_LOCALEID:
2423 if (!TryReadTwoStringFields(env, stateObj)) {
2424 return false;
2426 _defaultLCID = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
2427 break;
2429 case TdsEnums.ENV_COMPFLAGS:
2430 if (!TryReadTwoStringFields(env, stateObj)) {
2431 return false;
2433 break;
2435 case TdsEnums.ENV_COLLATION:
2436 Debug.Assert(env.newLength == 5 || env.newLength == 0, "Improper length in new collation!");
2437 if (!stateObj.TryReadByte(out byteLength)) {
2438 return false;
2440 env.newLength = byteLength;
2441 if (env.newLength == 5) {
2442 if (!TryProcessCollation(stateObj, out env.newCollation)) {
2443 return false;
2446 // give the parser the new collation values in case parameters don't specify one
2447 _defaultCollation = env.newCollation;
2448 int newCodePage = GetCodePage(env.newCollation, stateObj);
2449 if (newCodePage != _defaultCodePage) {
2450 _defaultCodePage = newCodePage;
2451 _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage);
2453 _defaultLCID = env.newCollation.LCID;
2456 if (!stateObj.TryReadByte(out byteLength)) {
2457 return false;
2459 env.oldLength = byteLength;
2460 Debug.Assert(env.oldLength == 5 || env.oldLength == 0, "Improper length in old collation!");
2461 if (env.oldLength == 5) {
2462 if (!TryProcessCollation(stateObj, out env.oldCollation)) {
2463 return false;
2467 env.length = 3 + env.newLength + env.oldLength;
2468 break;
2470 case TdsEnums.ENV_BEGINTRAN:
2471 case TdsEnums.ENV_COMMITTRAN:
2472 case TdsEnums.ENV_ROLLBACKTRAN:
2473 case TdsEnums.ENV_ENLISTDTC:
2474 case TdsEnums.ENV_DEFECTDTC:
2475 case TdsEnums.ENV_TRANSACTIONENDED:
2476 Debug.Assert(_isYukon, "Received new ENVCHANGE transaction/DTC token on pre 9.0 server!");
2478 if (!stateObj.TryReadByte(out byteLength)) {
2479 return false;
2481 env.newLength = byteLength;
2482 Debug.Assert(env.newLength == 0 || env.newLength == 8, "Improper length for new transaction id!");
2484 if (env.newLength > 0) {
2485 if (!stateObj.TryReadInt64(out env.newLongValue)) {
2486 return false;
2488 Debug.Assert(env.newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id.
2490 else {
2491 env.newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id.
2494 if (!stateObj.TryReadByte(out byteLength)) {
2495 return false;
2497 env.oldLength = byteLength;
2498 Debug.Assert(env.oldLength == 0 || env.oldLength == 8, "Improper length for old transaction id!");
2500 if (env.oldLength > 0) {
2501 if (!stateObj.TryReadInt64(out env.oldLongValue)) {
2502 return false;
2504 Debug.Assert(env.oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id.
2506 else {
2507 env.oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id.
2510 // env.length includes 1 byte type token
2511 env.length = 3 + env.newLength + env.oldLength;
2512 break;
2514 case TdsEnums.ENV_LOGSHIPNODE:
2515 // env.newBinValue is secondary node, env.oldBinValue is witness node
2516 // comes before LoginAck so we can't assert this
2517 if (!TryReadTwoStringFields(env, stateObj)) {
2518 return false;
2520 break;
2522 case TdsEnums.ENV_PROMOTETRANSACTION:
2523 Debug.Assert(_isYukon, "Received new ENVCHANGE tokens on pre 9.0 server!");
2525 if (!stateObj.TryReadInt32(out env.newLength)) { // new value has 4 byte length
2526 return false;
2528 env.newBinValue = new byte[env.newLength];
2529 if (!stateObj.TryReadByteArray(env.newBinValue, 0, env.newLength)) { // read new value with 4 byte length
2530 return false;
2533 if (!stateObj.TryReadByte(out byteLength)) {
2534 return false;
2536 env.oldLength = byteLength;
2537 Debug.Assert(0 == env.oldLength, "old length should be zero");
2539 // env.length includes 1 byte for type token
2540 env.length = 5 + env.newLength;
2541 break;
2543 case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS:
2544 case TdsEnums.ENV_SPRESETCONNECTIONACK:
2546 Debug.Assert(_isYukon, "Received new ENVCHANGE tokens on pre 9.0 server!");
2547 if (!TryReadTwoBinaryFields(env, stateObj)) {
2548 return false;
2550 break;
2552 case TdsEnums.ENV_USERINSTANCE:
2553 Debug.Assert(!_isYukon, "Received ENV_USERINSTANCE on non 9.0 server!");
2554 if (!TryReadTwoStringFields(env, stateObj)) {
2555 return false;
2557 break;
2559 case TdsEnums.ENV_ROUTING:
2560 ushort newLength;
2561 if (!stateObj.TryReadUInt16(out newLength)) {
2562 return false;
2564 env.newLength = newLength;
2565 byte protocol;
2566 if (!stateObj.TryReadByte(out protocol)) {
2567 return false;
2569 ushort port;
2570 if (!stateObj.TryReadUInt16(out port)) {
2571 return false;
2573 UInt16 serverLen;
2574 if (!stateObj.TryReadUInt16(out serverLen)) {
2575 return false;
2577 string serverName;
2578 if (!stateObj.TryReadString(serverLen, out serverName)) {
2579 return false;
2581 env.newRoutingInfo = new RoutingInfo(protocol, port, serverName);
2582 UInt16 oldLength;
2583 if (!stateObj.TryReadUInt16(out oldLength)) {
2584 return false;
2586 if (!stateObj.TrySkipBytes(oldLength)) {
2587 return false;
2589 env.length = env.newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength]
2590 break;
2592 default:
2593 Debug.Assert(false, "Unknown environment change token: " + env.type);
2594 break;
2596 processedLength += env.length;
2599 sqlEnvChange = envarray;
2600 return true;
2603 private bool TryReadTwoBinaryFields(SqlEnvChange env, TdsParserStateObject stateObj) {
2604 // Used by ProcessEnvChangeToken
2605 byte byteLength;
2606 if (!stateObj.TryReadByte(out byteLength)) {
2607 return false;
2609 env.newLength = byteLength;
2610 env.newBinValue = new byte[env.newLength];
2611 if (!stateObj.TryReadByteArray(env.newBinValue, 0, env.newLength)) {
2612 return false;
2614 if (!stateObj.TryReadByte(out byteLength)) {
2615 return false;
2617 env.oldLength = byteLength;
2618 env.oldBinValue = new byte[env.oldLength];
2619 if (!stateObj.TryReadByteArray(env.oldBinValue, 0, env.oldLength)) {
2620 return false;
2623 // env.length includes 1 byte type token
2624 env.length = 3 + env.newLength + env.oldLength;
2625 return true;
2628 private bool TryReadTwoStringFields(SqlEnvChange env, TdsParserStateObject stateObj) {
2629 // Used by ProcessEnvChangeToken
2630 byte newLength, oldLength;
2631 string newValue, oldValue;
2632 if (!stateObj.TryReadByte(out newLength)) {
2633 return false;
2635 if (!stateObj.TryReadString(newLength, out newValue)) {
2636 return false;
2638 if (!stateObj.TryReadByte(out oldLength)) {
2639 return false;
2641 if (!stateObj.TryReadString(oldLength, out oldValue)) {
2642 return false;
2645 env.newLength = newLength;
2646 env.newValue = newValue;
2647 env.oldLength = oldLength;
2648 env.oldValue = oldValue;
2650 // env.length includes 1 byte type token
2651 env.length = 3 + env.newLength * 2 + env.oldLength * 2;
2652 return true;
2655 private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavior run, TdsParserStateObject stateObj) {
2656 ushort curCmd;
2657 ushort status;
2658 int count;
2660 // Can't retry TryProcessDone
2661 stateObj._syncOverAsync = true;
2663 // status
2664 // command
2665 // rowcount (valid only if DONE_COUNT bit is set)
2667 if (!stateObj.TryReadUInt16(out status)) {
2668 return false;
2670 if (!stateObj.TryReadUInt16(out curCmd)) {
2671 return false;
2674 if (_isYukon) {
2675 long longCount;
2676 if (!stateObj.TryReadInt64(out longCount)) {
2677 return false;
2679 count = (int) longCount;
2681 else {
2682 if (!stateObj.TryReadInt32(out count)) {
2683 return false;
2685 // If we haven't yet completed processing login token stream yet, we may be talking to a Yukon server
2686 // In that case we still have to read another 4 bytes
2687 // But don't try to read beyond the TDS stream in this case, because it generates errors if login failed.
2688 if ( _state == TdsParserState.OpenNotLoggedIn) {
2689 // Login incomplete, if we are reading from Yukon we need to read another int
2690 if (stateObj._inBytesRead > stateObj._inBytesUsed) {
2691 byte b;
2692 if (!stateObj.TryPeekByte(out b)) {
2693 return false;
2695 if (b == 0) {
2696 // This is an invalid token value
2697 if (!stateObj.TryReadInt32(out count)) {
2698 return false;
2705 // We get a done token with the attention bit set
2706 if (TdsEnums.DONE_ATTN == (status & TdsEnums.DONE_ATTN)) {
2707 Debug.Assert(TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE),"Not expecting DONE_MORE when receiving DONE_ATTN");
2708 Debug.Assert(stateObj._attentionSent, "Received attention done without sending one!");
2709 stateObj._attentionReceived = true;
2710 Debug.Assert(stateObj._inBytesUsed == stateObj._inBytesRead && stateObj._inBytesPacket == 0, "DONE_ATTN received with more data left on wire");
2712 if ((null != cmd) && (TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) {
2713 if (curCmd != TdsEnums.SELECT) {
2714 if (cmd.IsDescribeParameterEncryptionRPCCurrentlyInProgress) {
2715 // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise.
2716 cmd.RowsAffectedByDescribeParameterEncryption = count;
2718 else {
2719 cmd.InternalRecordsAffected = count;
2722 // Skip the bogus DONE counts sent by the server
2723 if (stateObj._receivedColMetaData || (curCmd != TdsEnums.SELECT)) {
2724 cmd.OnStatementCompleted(count);
2728 stateObj._receivedColMetaData = false;
2730 // Surface exception for DONE_ERROR in the case we did not receive an error token
2731 // in the stream, but an error occurred. In these cases, we throw a general server error. The
2732 // situations where this can occur are: an invalid buffer received from client, login error
2733 // and the server refused our connection, and the case where we are trying to log in but
2734 // the server has reached its max connection limit. Bottom line, we need to throw general
2735 // error in the cases where we did not receive a error token along with the DONE_ERROR.
2736 if ((TdsEnums.DONE_ERROR == (TdsEnums.DONE_ERROR & status)) && stateObj.ErrorCount == 0 &&
2737 stateObj._errorTokenReceived == false && (RunBehavior.Clean != (RunBehavior.Clean & run))) {
2738 stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0));
2740 if (null != reader) { // SQL BU DT 269516
2741 if (!reader.IsInitialized) {
2742 run = RunBehavior.UntilDone;
2747 // Similar to above, only with a more severe error. In this case, if we received
2748 // the done_srverror, this exception will be added to the collection regardless.
2749 // MDAC #93896. Also, per Ashwin, the server will always break the connection in this case.
2750 if ((TdsEnums.DONE_SRVERROR == (TdsEnums.DONE_SRVERROR & status)) && (RunBehavior.Clean != (RunBehavior.Clean & run))) {
2751 stateObj.AddError(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0));
2753 if (null != reader) { // SQL BU DT 269516
2754 if (!reader.IsInitialized) {
2755 run = RunBehavior.UntilDone;
2760 ProcessSqlStatistics(curCmd, status, count);
2762 // stop if the DONE_MORE bit isn't set (see above for attention handling)
2763 if (TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE)) {
2764 stateObj._errorTokenReceived = false;
2765 if (stateObj._inBytesUsed >= stateObj._inBytesRead) {
2766 stateObj._pendingData = false;
2770 // _pendingData set by e.g. 'TdsExecuteSQLBatch'
2771 // _hasOpenResult always set to true by 'WriteMarsHeader'
2773 if (!stateObj._pendingData && stateObj._hasOpenResult) {
2775 Debug.Assert(!((sqlTransaction != null && _distributedTransaction != null) ||
2776 (_userStartedLocalTransaction != null && _distributedTransaction != null))
2777 , "ProcessDone - have both distributed and local transactions not null!");
2778 */ // WebData 112722
2780 stateObj.DecrementOpenResultCount();
2783 return true;
2786 private void ProcessSqlStatistics(ushort curCmd, ushort status, int count) {
2787 // SqlStatistics bookkeeping stuff
2789 if (null != _statistics) {
2790 // any done after row(s) counts as a resultset
2791 if (_statistics.WaitForDoneAfterRow) {
2792 _statistics.SafeIncrement(ref _statistics._sumResultSets);
2793 _statistics.WaitForDoneAfterRow = false;
2796 // clear row count DONE_COUNT flag is not set
2797 if (!(TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) {
2798 count = 0;
2801 switch (curCmd) {
2802 case TdsEnums.INSERT:
2803 case TdsEnums.DELETE:
2804 case TdsEnums.UPDATE:
2805 case TdsEnums.MERGE:
2806 _statistics.SafeIncrement(ref _statistics._iduCount);
2807 _statistics.SafeAdd(ref _statistics._iduRows, count);
2808 if (!_statisticsIsInTransaction) {
2809 _statistics.SafeIncrement(ref _statistics._transactions);
2812 break;
2814 case TdsEnums.SELECT:
2815 _statistics.SafeIncrement(ref _statistics._selectCount);
2816 _statistics.SafeAdd(ref _statistics._selectRows, count);
2817 break;
2819 case TdsEnums.BEGINXACT:
2820 if (!_statisticsIsInTransaction) {
2821 _statistics.SafeIncrement(ref _statistics._transactions);
2823 _statisticsIsInTransaction = true;
2824 break;
2826 case TdsEnums.OPENCURSOR:
2827 _statistics.SafeIncrement(ref _statistics._cursorOpens);
2828 break;
2830 case TdsEnums.ABORT:
2831 _statisticsIsInTransaction = false;
2832 break;
2834 case TdsEnums.ENDXACT:
2835 _statisticsIsInTransaction = false;
2836 break;
2837 } // switch
2839 else {
2840 switch (curCmd) {
2841 case TdsEnums.BEGINXACT:
2842 _statisticsIsInTransaction = true;
2843 break;
2845 case TdsEnums.ABORT:
2846 case TdsEnums.ENDXACT:
2847 _statisticsIsInTransaction = false;
2848 break;
2853 private bool TryProcessFeatureExtAck(TdsParserStateObject stateObj) {
2854 // read feature ID
2855 byte featureId;
2856 do {
2857 if (!stateObj.TryReadByte(out featureId)) {
2858 return false;
2860 if (featureId != TdsEnums.FEATUREEXT_TERMINATOR) {
2861 UInt32 dataLen;
2862 if (!stateObj.TryReadUInt32(out dataLen)) {
2863 return false;
2865 byte[] data = new byte[dataLen];
2866 if (dataLen > 0) {
2867 if (!stateObj.TryReadByteArray(data, 0, checked ((int)dataLen))) {
2868 return false;
2871 _connHandler.OnFeatureExtAck(featureId, data);
2873 } while (featureId != TdsEnums.FEATUREEXT_TERMINATOR);
2875 // Check if column encryption was on and feature wasn't acknowledged.
2876 if (_connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled && !IsColumnEncryptionSupported) {
2877 throw SQL.TceNotSupported ();
2880 return true;
2883 private bool TryProcessSessionState(TdsParserStateObject stateObj, int length, SessionData sdata) {
2884 if (length < 5) {
2885 throw SQL.ParsingErrorLength(ParsingErrorState.SessionStateLengthTooShort, length);
2887 UInt32 seqNum;
2888 if (!stateObj.TryReadUInt32(out seqNum)) {
2889 return false;
2891 if (seqNum == UInt32.MaxValue) {
2892 _connHandler.DoNotPoolThisConnection();
2894 byte status;
2895 if (!stateObj.TryReadByte(out status)) {
2896 return false;
2898 if (status > 1) {
2899 throw SQL.ParsingErrorStatus(ParsingErrorState.SessionStateInvalidStatus, status);
2901 bool recoverable = status != 0;
2902 length -= 5;
2903 while (length > 0) {
2904 byte stateId;
2905 if (!stateObj.TryReadByte(out stateId)) {
2906 return false;
2908 int stateLen;
2909 byte stateLenByte;
2910 if (!stateObj.TryReadByte(out stateLenByte)) {
2911 return false;
2913 if (stateLenByte < 0xFF) {
2914 stateLen = stateLenByte;
2916 else {
2917 if (!stateObj.TryReadInt32(out stateLen)) {
2918 return false;
2921 byte[] buffer = null;
2922 lock (sdata._delta) {
2923 if (sdata._delta[stateId] == null) {
2924 buffer = new byte[stateLen];
2925 sdata._delta[stateId] = new SessionStateRecord { _version = seqNum, _dataLength = stateLen, _data = buffer, _recoverable = recoverable };
2926 sdata._deltaDirty = true;
2927 if (!recoverable) {
2928 checked { sdata._unrecoverableStatesCount++; }
2931 else {
2932 if (sdata._delta[stateId]._version <= seqNum) {
2933 SessionStateRecord sv = sdata._delta[stateId];
2934 sv._version = seqNum;
2935 sv._dataLength = stateLen;
2936 if (sv._recoverable != recoverable) {
2937 if (recoverable) {
2938 Debug.Assert(sdata._unrecoverableStatesCount > 0, "Unrecoverable states count >0");
2939 sdata._unrecoverableStatesCount--;
2941 else {
2942 checked { sdata._unrecoverableStatesCount++; }
2944 sv._recoverable = recoverable;
2946 buffer = sv._data;
2947 if (buffer.Length < stateLen) {
2948 buffer = new byte[stateLen];
2949 sv._data = buffer;
2954 if (buffer != null) {
2955 if (!stateObj.TryReadByteArray(buffer, 0, stateLen)) {
2956 return false;
2959 else {
2960 if (!stateObj.TrySkipBytes(stateLen))
2961 return false;
2964 if (stateLenByte < 0xFF) {
2965 length -= 2 + stateLen;
2967 else {
2968 length -= 6 + stateLen;
2971 sdata.AssertUnrecoverableStateCountIsCorrect();
2973 return true;
2976 private bool TryProcessLoginAck(TdsParserStateObject stateObj, out SqlLoginAck sqlLoginAck) {
2977 SqlLoginAck a = new SqlLoginAck();
2979 sqlLoginAck = null;
2981 // read past interface type and version
2982 if (!stateObj.TrySkipBytes(1)) {
2983 return false;
2986 byte[] b = new byte[TdsEnums.VERSION_SIZE];
2987 if (!stateObj.TryReadByteArray(b, 0, b.Length)) {
2988 return false;
2990 a.tdsVersion = (UInt32)((((((b[0]<<8)|b[1])<<8)|b[2])<<8)|b[3]); // bytes are in motorola order (high byte first)
2991 UInt32 majorMinor = a.tdsVersion & 0xff00ffff;
2992 UInt32 increment = (a.tdsVersion >> 16) & 0xff;
2994 // Server responds:
2995 // 0x07000000 -> Sphinx // Notice server response format is different for bwd compat
2996 // 0x07010000 -> Shiloh RTM // Notice server response format is different for bwd compat
2997 // 0x71000001 -> Shiloh SP1
2998 // 0x72xx0002 -> Yukon RTM
2999 // information provided by S. Ashwin
3001 switch (majorMinor) {
3002 case TdsEnums.SPHINXORSHILOH_MAJOR<<24|TdsEnums.DEFAULT_MINOR: // Sphinx & Shiloh RTM
3003 // note that sphinx and shiloh_rtm can only be distinguished by the increment
3004 switch (increment) {
3005 case TdsEnums.SHILOH_INCREMENT:
3006 _isShiloh = true;
3007 break;
3008 case TdsEnums.SPHINX_INCREMENT:
3009 // no flag will be set
3010 break;
3011 default:
3012 throw SQL.InvalidTDSVersion();
3014 break;
3015 case TdsEnums.SHILOHSP1_MAJOR<<24|TdsEnums.SHILOHSP1_MINOR: // Shiloh SP1
3016 if (increment != TdsEnums.SHILOHSP1_INCREMENT) { throw SQL.InvalidTDSVersion(); }
3017 _isShilohSP1 = true;
3018 break;
3019 case TdsEnums.YUKON_MAJOR<<24|TdsEnums.YUKON_RTM_MINOR: // Yukon
3020 if (increment != TdsEnums.YUKON_INCREMENT) { throw SQL.InvalidTDSVersion(); }
3021 _isYukon = true;
3022 break;
3023 case TdsEnums.KATMAI_MAJOR<<24|TdsEnums.KATMAI_MINOR:
3024 if (increment != TdsEnums.KATMAI_INCREMENT) { throw SQL.InvalidTDSVersion(); }
3025 _isKatmai = true;
3026 break;
3027 case TdsEnums.DENALI_MAJOR << 24|TdsEnums.DENALI_MINOR:
3028 if (increment != TdsEnums.DENALI_INCREMENT) { throw SQL.InvalidTDSVersion(); }
3029 _isDenali = true;
3030 break;
3031 default:
3032 throw SQL.InvalidTDSVersion();
3035 _isKatmai |= _isDenali;
3036 _isYukon |= _isKatmai;
3037 _isShilohSP1 |= _isYukon; // includes all lower versions
3038 _isShiloh |= _isShilohSP1; //
3040 a.isVersion8 = _isShiloh;
3042 stateObj._outBytesUsed = stateObj._outputHeaderLen;
3043 byte len;
3044 if (!stateObj.TryReadByte(out len)) {
3045 return false;
3048 if (!stateObj.TryReadString(len, out a.programName)) {
3049 return false;
3051 if (!stateObj.TryReadByte(out a.majorVersion)) {
3052 return false;
3054 if (!stateObj.TryReadByte(out a.minorVersion)) {
3055 return false;
3057 byte buildNumHi, buildNumLo;
3058 if (!stateObj.TryReadByte(out buildNumHi)) {
3059 return false;
3061 if (!stateObj.TryReadByte(out buildNumLo)) {
3062 return false;
3065 a.buildNum = (short)((buildNumHi << 8) + buildNumLo);
3067 Debug.Assert(_state == TdsParserState.OpenNotLoggedIn, "ProcessLoginAck called with state not TdsParserState.OpenNotLoggedIn");
3068 _state = TdsParserState.OpenLoggedIn;
3070 if (_isYukon) {
3071 if (_fMARS) {
3072 _resetConnectionEvent = new AutoResetEvent(true);
3076 // Fail if SSE UserInstance and we have not received this info.
3077 if ( _connHandler.ConnectionOptions.UserInstance &&
3078 ADP.IsEmpty(_connHandler.InstanceName)) {
3079 stateObj.AddError(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, Server, SQLMessage.UserInstanceFailure(), "", 0));
3080 ThrowExceptionAndWarning(stateObj);
3083 sqlLoginAck = a;
3084 return true;
3087 private bool TryProcessFedAuthInfo(TdsParserStateObject stateObj, int tokenLen, out SqlFedAuthInfo sqlFedAuthInfo) {
3088 sqlFedAuthInfo = null;
3089 SqlFedAuthInfo tempFedAuthInfo = new SqlFedAuthInfo();
3091 // Skip reading token length, since it has already been read in caller
3093 if (Bid.AdvancedOn) {
3094 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> FEDAUTHINFO token stream length = {0}\n", tokenLen);
3097 if (tokenLen < sizeof(uint)) {
3098 // the token must at least contain a DWORD indicating the number of info IDs
3099 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream length too short for CountOfInfoIDs.\n");
3100 throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForCountOfInfoIds, tokenLen);
3103 // read how many FedAuthInfo options there are
3104 uint optionsCount;
3105 if (!stateObj.TryReadUInt32(out optionsCount)) {
3106 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> Failed to read CountOfInfoIDs in FEDAUTHINFO token stream.\n");
3107 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadCountOfInfoIds);
3109 tokenLen -= sizeof(uint); // remaining length is shortened since we read optCount
3111 if (Bid.AdvancedOn) {
3112 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> CountOfInfoIDs = {0}\n", optionsCount.ToString(CultureInfo.InvariantCulture));
3115 if (tokenLen > 0) {
3116 // read the rest of the token
3117 byte[] tokenData = new byte[tokenLen];
3118 int totalRead = 0;
3119 bool successfulRead = stateObj.TryReadByteArray(tokenData, 0, tokenLen, out totalRead);
3121 if (Bid.AdvancedOn) {
3122 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> Read rest of FEDAUTHINFO token stream: {0}\n", BitConverter.ToString(tokenData, 0, totalRead));
3125 if (!successfulRead || totalRead != tokenLen) {
3126 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> Failed to read FEDAUTHINFO token stream. Attempted to read {0} bytes, actually read {1}\n", tokenLen, totalRead);
3127 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadTokenStream);
3130 // each FedAuthInfoOpt is 9 bytes:
3131 // 1 byte for FedAuthInfoID
3132 // 4 bytes for FedAuthInfoDataLen
3133 // 4 bytes for FedAuthInfoDataOffset
3134 // So this is the index in tokenData for the i-th option
3135 const uint optionSize = 9;
3137 // the total number of bytes for all FedAuthInfoOpts together
3138 uint totalOptionsSize = checked(optionsCount * optionSize);
3140 for (uint i = 0; i < optionsCount; i++) {
3141 uint currentOptionOffset = checked(i * optionSize);
3143 byte id = tokenData[currentOptionOffset];
3144 uint dataLen = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 1)));
3145 uint dataOffset = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 5)));
3147 if (Bid.AdvancedOn) {
3148 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> FedAuthInfoOpt: ID={0}, DataLen={1}, Offset={2}\n", id, dataLen.ToString(CultureInfo.InvariantCulture), dataOffset.ToString(CultureInfo.InvariantCulture));
3151 // offset is measured from optCount, so subtract to make offset measured
3152 // from the beginning of tokenData
3153 checked {
3154 dataOffset -= sizeof(uint);
3157 // if dataOffset points to a region within FedAuthInfoOpt or after the end of the token, throw
3158 if (dataOffset < totalOptionsSize || dataOffset >= tokenLen) {
3159 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FedAuthInfoDataOffset points to an invalid location.\n");
3160 throw SQL.ParsingErrorOffset(ParsingErrorState.FedAuthInfoInvalidOffset, unchecked((int)dataOffset));
3163 // try to read data and throw if the arguments are bad, meaning the server sent us a bad token
3164 string data;
3165 try {
3166 data = System.Text.Encoding.Unicode.GetString(tokenData, checked((int)dataOffset), checked((int)dataLen));
3168 catch (ArgumentOutOfRangeException e) {
3169 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> Failed to read FedAuthInfoData.\n");
3170 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadData, e);
3172 catch (ArgumentException e) {
3173 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FedAuthInfoData is not in unicode format.\n");
3174 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoDataNotUnicode, e);
3177 if (Bid.AdvancedOn) {
3178 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> FedAuthInfoData: {0}\n", data);
3181 // store data in tempFedAuthInfo
3182 switch ((TdsEnums.FedAuthInfoId)id) {
3183 case TdsEnums.FedAuthInfoId.Spn:
3184 tempFedAuthInfo.spn = data;
3185 break;
3186 case TdsEnums.FedAuthInfoId.Stsurl:
3187 tempFedAuthInfo.stsurl = data;
3188 break;
3189 default:
3190 if (Bid.AdvancedOn) {
3191 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> Ignoring unknown federated authentication info option: {0}\n", id);
3193 break;
3197 else {
3198 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream is not long enough to contain the data it claims to.\n");
3199 throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForData, tokenLen);
3202 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> Processed FEDAUTHINFO token stream: {0}\n", tempFedAuthInfo.ToString());
3204 if (String.IsNullOrWhiteSpace(tempFedAuthInfo.stsurl) || String.IsNullOrWhiteSpace(tempFedAuthInfo.spn)) {
3205 // We should be receiving both stsurl and spn
3206 Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream does not contain both STSURL and SPN.\n");
3207 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoDoesNotContainStsurlAndSpn);
3210 sqlFedAuthInfo = tempFedAuthInfo;
3211 return true;
3214 internal bool TryProcessError(byte token, TdsParserStateObject stateObj, out SqlError error) {
3215 ushort shortLen;
3216 byte byteLen;
3217 int number;
3218 byte state;
3219 byte errorClass;
3221 error = null;
3223 if (!stateObj.TryReadInt32(out number)) {
3224 return false;
3226 if (!stateObj.TryReadByte(out state)) {
3227 return false;
3229 if (!stateObj.TryReadByte(out errorClass)) {
3230 return false;
3233 Debug.Assert(((errorClass >= TdsEnums.MIN_ERROR_CLASS) && token == TdsEnums.SQLERROR) ||
3234 ((errorClass < TdsEnums.MIN_ERROR_CLASS) && token == TdsEnums.SQLINFO), "class and token don't match!");
3236 if (!stateObj.TryReadUInt16(out shortLen)) {
3237 return false;
3239 string message;
3240 if (!stateObj.TryReadString(shortLen, out message)) {
3241 return false;
3244 if (!stateObj.TryReadByte(out byteLen)) {
3245 return false;
3248 string server;
3250 // MDAC bug #49307 - server sometimes does not send over server field! In those cases
3251 // we will use our locally cached value.
3252 if (byteLen == 0) {
3253 server = _server;
3255 else {
3256 if (!stateObj.TryReadString(byteLen, out server)) {
3257 return false;
3261 if (!stateObj.TryReadByte(out byteLen)) {
3262 return false;
3264 string procedure;
3265 if (!stateObj.TryReadString(byteLen, out procedure)) {
3266 return false;
3269 int line;
3270 if (_isYukon) {
3271 if (!stateObj.TryReadInt32(out line)) {
3272 return false;
3275 else {
3276 ushort shortLine;
3277 if (!stateObj.TryReadUInt16(out shortLine)) {
3278 return false;
3280 line = shortLine;
3281 // If we haven't yet completed processing login token stream yet, we may be talking to a Yukon server
3282 // In that case we still have to read another 2 bytes
3283 if ( _state == TdsParserState.OpenNotLoggedIn) {
3284 // Login incomplete
3285 byte b;
3286 if (!stateObj.TryPeekByte(out b)) {
3287 return false;
3289 if (b == 0) {
3290 // This is an invalid token value
3291 ushort value;
3292 if (!stateObj.TryReadUInt16(out value)) {
3293 return false;
3295 line = (line << 16) + value;
3300 error = new SqlError(number, state, errorClass, _server, message, procedure, line);
3301 return true;
3305 internal bool TryProcessReturnValue(int length,
3306 TdsParserStateObject stateObj,
3307 out SqlReturnValue returnValue,
3308 SqlCommandColumnEncryptionSetting columnEncryptionSetting) {
3309 returnValue = null;
3310 SqlReturnValue rec = new SqlReturnValue();
3311 rec.length = length; // In Yukon this length is -1
3312 if (_isYukon) {
3313 if (!stateObj.TryReadUInt16(out rec.parmIndex)) {
3314 return false;
3317 byte len;
3318 if (!stateObj.TryReadByte(out len)) { // Length of parameter name
3319 return false;
3322 rec.parameter = null;
3323 if (len > 0) {
3324 if (!stateObj.TryReadString(len, out rec.parameter)) {
3325 return false;
3329 // read status and ignore
3330 byte ignored;
3331 if (!stateObj.TryReadByte(out ignored)) {
3332 return false;
3335 UInt32 userType;
3337 // read user type - 4 bytes Yukon, 2 backwards
3338 if (IsYukonOrNewer) {
3339 if (!stateObj.TryReadUInt32(out userType)) {
3340 return false;
3343 else {
3344 ushort userTypeShort;
3345 if (!stateObj.TryReadUInt16(out userTypeShort)) {
3346 return false;
3348 userType = userTypeShort;
3351 // Read off the flags.
3352 // The first byte is ignored since it doesn't contain any interesting information.
3353 byte flags;
3354 if (!stateObj.TryReadByte(out flags)) {
3355 return false;
3358 if (!stateObj.TryReadByte(out flags)) {
3359 return false;
3362 // Check if the column is encrypted.
3363 if (_serverSupportsColumnEncryption) {
3364 rec.isEncrypted = (TdsEnums.IsEncrypted == (flags & TdsEnums.IsEncrypted));
3367 // read the type
3368 byte tdsType;
3369 if (!stateObj.TryReadByte(out tdsType)) {
3370 return false;
3373 // read the MaxLen
3374 // For xml datatpyes, there is no tokenLength
3375 int tdsLen;
3377 if (tdsType == TdsEnums.SQLXMLTYPE) {
3378 tdsLen = TdsEnums.SQL_USHORTVARMAXLEN;
3380 else if (IsVarTimeTds(tdsType))
3381 tdsLen = 0; // placeholder until we read the scale, just make sure it's not SQL_USHORTVARMAXLEN
3382 else if (tdsType == TdsEnums.SQLDATE) {
3383 tdsLen = 3;
3385 else {
3386 if (!TryGetTokenLength(tdsType, stateObj, out tdsLen)) {
3387 return false;
3391 rec.metaType = MetaType.GetSqlDataType(tdsType, userType, tdsLen);
3392 rec.type = rec.metaType.SqlDbType;
3394 // always use the nullable type for parameters if Shiloh or later
3395 // Sphinx sometimes sends fixed length return values
3396 if (_isShiloh) {
3397 rec.tdsType = rec.metaType.NullableType;
3398 rec.isNullable = true;
3399 if (tdsLen == TdsEnums.SQL_USHORTVARMAXLEN) {
3400 Debug.Assert(_isYukon, "plp data from pre-Yukon server");
3401 rec.metaType = MetaType.GetMaxMetaTypeFromMetaType(rec.metaType);
3404 else { // For sphinx, keep the fixed type if that is what is returned
3405 if (rec.metaType.NullableType == tdsType)
3406 rec.isNullable = true;
3408 rec.tdsType = (byte)tdsType;
3411 if (rec.type == SqlDbType.Decimal) {
3412 if (!stateObj.TryReadByte(out rec.precision)) {
3413 return false;
3415 if (!stateObj.TryReadByte(out rec.scale)) {
3416 return false;
3420 if (rec.metaType.IsVarTime) {
3421 if (!stateObj.TryReadByte(out rec.scale)) {
3422 return false;
3426 if (tdsType == TdsEnums.SQLUDT) {
3427 if (!TryProcessUDTMetaData((SqlMetaDataPriv) rec, stateObj)) {
3428 return false;
3432 if (rec.type == SqlDbType.Xml) {
3433 // Read schema info
3434 byte schemapresent;
3435 if (!stateObj.TryReadByte(out schemapresent)) {
3436 return false;
3439 if ((schemapresent & 1) != 0) {
3440 if (!stateObj.TryReadByte(out len)) {
3441 return false;
3443 if (len != 0) {
3444 if (!stateObj.TryReadString(len, out rec.xmlSchemaCollectionDatabase)) {
3445 return false;
3449 if (!stateObj.TryReadByte(out len)) {
3450 return false;
3452 if (len != 0) {
3453 if (!stateObj.TryReadString(len, out rec.xmlSchemaCollectionOwningSchema)) {
3454 return false;
3458 short slen;
3459 if (!stateObj.TryReadInt16(out slen)) {
3460 return false;
3463 if (slen != 0) {
3464 if (!stateObj.TryReadString(slen, out rec.xmlSchemaCollectionName)) {
3465 return false;
3471 else if (_isShiloh && rec.metaType.IsCharType) {
3472 // read the collation for 8.x servers
3473 if (!TryProcessCollation(stateObj, out rec.collation)) {
3474 return false;
3477 int codePage = GetCodePage(rec.collation, stateObj);
3479 // if the column lcid is the same as the default, use the default encoder
3480 if (codePage == _defaultCodePage) {
3481 rec.codePage = _defaultCodePage;
3482 rec.encoding = _defaultEncoding;
3484 else {
3485 rec.codePage = codePage;
3486 rec.encoding = System.Text.Encoding.GetEncoding(rec.codePage);
3490 // For encrypted parameters, read the unencrypted type and encryption information.
3491 if (_serverSupportsColumnEncryption && rec.isEncrypted) {
3492 if (!TryProcessTceCryptoMetadata(stateObj, rec, cipherTable: null, columnEncryptionSetting: columnEncryptionSetting, isReturnValue: true)) {
3493 return false;
3497 // for now we coerce return values into a SQLVariant, not good...
3498 bool isNull = false;
3499 ulong valLen;
3500 if (!TryProcessColumnHeaderNoNBC(rec, stateObj, out isNull, out valLen)) {
3501 return false;
3504 // always read as sql types
3505 Debug.Assert(valLen < (ulong)(Int32.MaxValue), "ProcessReturnValue received data size > 2Gb");
3507 int intlen = valLen > (ulong)(Int32.MaxValue) ? Int32.MaxValue : (int)valLen;
3509 if (rec.metaType.IsPlp) {
3510 intlen = Int32.MaxValue; // If plp data, read it all
3513 if (isNull) {
3514 GetNullSqlValue(rec.value, rec, SqlCommandColumnEncryptionSetting.Disabled, _connHandler);
3516 else {
3517 // We should never do any decryption here, so pass disabled as the command encryption override.
3518 // We only read the binary value and decryption will be performed by OnReturnValue().
3519 if (!TryReadSqlValue(rec.value, rec, intlen, stateObj, SqlCommandColumnEncryptionSetting.Disabled, columnName:null /*Not used*/)) {
3520 return false;
3524 returnValue = rec;
3525 return true;
3528 internal bool TryProcessTceCryptoMetadata (TdsParserStateObject stateObj,
3529 SqlMetaDataPriv col,
3530 SqlTceCipherInfoTable? cipherTable,
3531 SqlCommandColumnEncryptionSetting columnEncryptionSetting,
3532 bool isReturnValue) {
3533 Debug.Assert(isReturnValue == (cipherTable == null), "Ciphertable is not set iff this is a return value");
3535 // Read the ordinal into cipher table
3536 ushort index = 0;
3537 UInt32 userType;
3539 // For return values there is not cipher table and no ordinal.
3540 if (cipherTable.HasValue) {
3541 if (!stateObj.TryReadUInt16(out index)) {
3542 return false;
3545 // validate the index (ordinal passed)
3546 if (index >= cipherTable.Value.Size) {
3547 Bid.Trace("<sc.TdsParser.TryProcessTceCryptoMetadata|TCE> Incorrect ordinal received %d, max tab size: %d\n", index, cipherTable.Value.Size);
3548 throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidOrdinalIntoCipherInfoTable, index);
3552 // Read the user type
3553 if (!stateObj.TryReadUInt32(out userType)) {
3554 return false;
3557 // Read the base TypeInfo
3558 col.baseTI = new SqlMetaDataPriv();
3559 if (!TryProcessTypeInfo(stateObj, col.baseTI, userType)) {
3560 return false;
3563 // Read the cipher algorithm Id
3564 byte cipherAlgorithmId;
3565 if (!stateObj.TryReadByte(out cipherAlgorithmId)) {
3566 return false;
3569 string cipherAlgorithmName = null;
3570 if (TdsEnums.CustomCipherAlgorithmId == cipherAlgorithmId) {
3571 // Custom encryption algorithm, read the name
3572 byte nameSize;
3573 if (!stateObj.TryReadByte(out nameSize)) {
3574 return false;
3577 if (!stateObj.TryReadString(nameSize, out cipherAlgorithmName)) {
3578 return false;
3582 // Read Encryption Type.
3583 byte encryptionType;
3584 if (!stateObj.TryReadByte(out encryptionType)) {
3585 return false;
3588 // Read Normalization Rule Version.
3589 byte normalizationRuleVersion;
3590 if (!stateObj.TryReadByte(out normalizationRuleVersion)) {
3591 return false;
3594 Debug.Assert(col.cipherMD == null, "col.cipherMD should be null in TryProcessTceCryptoMetadata.");
3596 // Check if TCE is enable and if it is set the crypto MD for the column.
3597 // TCE is enabled if the command is set to enabled or to resultset only and this is not a return value
3598 // or if it is set to use connection setting and the connection has TCE enabled.
3599 if ((columnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled ||
3600 (columnEncryptionSetting == SqlCommandColumnEncryptionSetting.ResultSetOnly && !isReturnValue)) ||
3601 (columnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting &&
3602 _connHandler != null && _connHandler.ConnectionOptions != null &&
3603 _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled)) {
3604 col.cipherMD = new SqlCipherMetadata(cipherTable.HasValue ? (SqlTceCipherInfoEntry?)cipherTable.Value[index] : null,
3605 index,
3606 cipherAlgorithmId: cipherAlgorithmId,
3607 cipherAlgorithmName: cipherAlgorithmName,
3608 encryptionType: encryptionType,
3609 normalizationRuleVersion: normalizationRuleVersion);
3611 else {
3612 // If TCE is disabled mark the MD as not encrypted.
3613 col.isEncrypted = false;
3616 return true;
3619 internal bool TryProcessCollation(TdsParserStateObject stateObj, out SqlCollation collation) {
3620 SqlCollation newCollation = new SqlCollation();
3622 if (!stateObj.TryReadUInt32(out newCollation.info)) {
3623 collation = null;
3624 return false;
3626 if (!stateObj.TryReadByte(out newCollation.sortId)) {
3627 collation = null;
3628 return false;
3631 collation = newCollation;
3632 return true;
3635 private void WriteCollation(SqlCollation collation, TdsParserStateObject stateObj) {
3636 if (collation == null) {
3637 _physicalStateObj.WriteByte(0);
3639 else {
3640 _physicalStateObj.WriteByte(sizeof(UInt32)+sizeof(byte));
3641 WriteUnsignedInt(collation.info, _physicalStateObj);
3642 _physicalStateObj.WriteByte(collation.sortId);
3647 internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) {
3648 int codePage = 0;
3650 if (0 != collation.sortId) {
3651 codePage = TdsEnums.CODE_PAGE_FROM_SORT_ID[collation.sortId];
3652 Debug.Assert(0 != codePage, "GetCodePage accessed codepage array and produced 0!, sortID =" + ((Byte)(collation.sortId)).ToString((IFormatProvider)null));
3654 else {
3655 int cultureId = collation.LCID;
3656 bool success = false;
3658 try {
3659 codePage = CultureInfo.GetCultureInfo(cultureId).TextInfo.ANSICodePage;
3661 // SqlHot 50001398: CodePage can be zero, but we should defer such errors until
3662 // we actually MUST use the code page (i.e. don't error if no ANSI data is sent).
3663 success = true;
3665 catch (ArgumentException e) {
3666 ADP.TraceExceptionWithoutRethrow(e);
3669 // If we failed, it is quite possible this is because certain culture id's
3670 // were removed in Win2k and beyond, however Sql Server still supports them.
3671 // There is a workaround for the culture id's listed below, which is to mask
3672 // off the sort id (the leading 1). If that fails, or we have a culture id
3673 // other than the special cases below, we throw an error and throw away the
3674 // rest of the results. For additional info, see MDAC 65963.
3676 // SqlHot 50001398: Sometimes GetCultureInfo will return CodePage 0 instead of throwing.
3677 // treat this as an error also, and switch into the special-case logic.
3678 if (!success || codePage == 0) {
3679 CultureInfo ci = null;
3680 switch (cultureId) {
3681 case 0x10404: // zh-TW
3682 case 0x10804: // zh-CN
3683 case 0x10c04: // zh-HK
3684 case 0x11004: // zh-SG
3685 case 0x11404: // zh-MO
3686 case 0x10411: // ja-JP
3687 case 0x10412: // ko-KR
3688 // If one of the following special cases, mask out sortId and
3689 // retry.
3690 cultureId = cultureId & 0x03fff;
3692 try {
3693 ci = new CultureInfo(cultureId);
3694 success = true;
3696 catch (ArgumentException e) {
3697 ADP.TraceExceptionWithoutRethrow(e);
3699 break;
3700 case 0x827: // Non-supported Lithuanian code page, map it to supported Lithuanian.
3701 try {
3702 ci = new CultureInfo(0x427);
3703 success = true;
3705 catch (ArgumentException e) {
3706 ADP.TraceExceptionWithoutRethrow(e);
3708 break;
3709 default:
3710 break;
3713 // I don't believe we should still be in failure case, but just in case.
3714 if (!success) {
3715 ThrowUnsupportedCollationEncountered(stateObj);
3718 if (null != ci) {
3719 codePage = ci.TextInfo.ANSICodePage;
3724 return codePage;
3728 internal void DrainData(TdsParserStateObject stateObj) {
3729 RuntimeHelpers.PrepareConstrainedRegions();
3730 try {
3731 #if DEBUG
3732 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
3734 RuntimeHelpers.PrepareConstrainedRegions();
3735 try {
3736 tdsReliabilitySection.Start();
3737 #else
3739 #endif //DEBUG
3740 try {
3741 SqlDataReader.SharedState sharedState = stateObj._readerState;
3742 if (sharedState != null && sharedState._dataReady) {
3743 var metadata = stateObj._cleanupMetaData;
3744 if (stateObj._partialHeaderBytesRead > 0) {
3745 if (!stateObj.TryProcessHeader()) {
3746 throw SQL.SynchronousCallMayNotPend();
3749 if (0 == sharedState._nextColumnHeaderToRead) {
3750 // i. user called read but didn't fetch anything
3751 if (!stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj)) {
3752 throw SQL.SynchronousCallMayNotPend();
3755 else {
3756 // iia. if we still have bytes left from a partially read column, skip
3757 if (sharedState._nextColumnDataToRead < sharedState._nextColumnHeaderToRead) {
3758 if ((sharedState._nextColumnHeaderToRead > 0) && (metadata[sharedState._nextColumnHeaderToRead - 1].metaType.IsPlp)) {
3759 if (stateObj._longlen != 0) {
3760 ulong ignored;
3761 if (!TrySkipPlpValue(UInt64.MaxValue, stateObj, out ignored)) {
3762 throw SQL.SynchronousCallMayNotPend();
3767 else if (0 < sharedState._columnDataBytesRemaining) {
3768 if (!stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining)) {
3769 throw SQL.SynchronousCallMayNotPend();
3776 // iib.
3777 // now read the remaining values off the wire for this row
3778 if (!stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj)) {
3779 throw SQL.SynchronousCallMayNotPend();
3784 Run(RunBehavior.Clean, null, null, null, stateObj);
3786 catch {
3787 _connHandler.DoomThisConnection();
3788 throw;
3791 #if DEBUG
3792 finally {
3793 tdsReliabilitySection.Stop();
3795 #endif //DEBUG
3797 catch (System.OutOfMemoryException) {
3798 _connHandler.DoomThisConnection();
3799 throw;
3801 catch (System.StackOverflowException) {
3802 _connHandler.DoomThisConnection();
3803 throw;
3805 catch (System.Threading.ThreadAbortException) {
3806 _connHandler.DoomThisConnection();
3807 throw;
3812 internal void ThrowUnsupportedCollationEncountered(TdsParserStateObject stateObj) {
3813 stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.CultureIdError(), "", 0));
3815 if (null != stateObj) {
3816 DrainData(stateObj);
3818 stateObj._pendingData = false;
3821 ThrowExceptionAndWarning(stateObj);
3826 internal bool TryProcessAltMetaData(int cColumns, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData) {
3827 Debug.Assert(cColumns > 0, "should have at least 1 column in altMetaData!");
3829 metaData = null;
3830 _SqlMetaDataSet altMetaDataSet = new _SqlMetaDataSet(cColumns, null);
3831 int[] indexMap = new int[cColumns];
3833 if (!stateObj.TryReadUInt16(out altMetaDataSet.id)) {
3834 return false;
3837 byte byCols;
3838 if (!stateObj.TryReadByte(out byCols)) {
3839 return false;
3842 while (byCols > 0) {
3843 if (!stateObj.TrySkipBytes(2)) { // ignore ColNum ...
3844 return false;
3846 byCols--;
3849 // pass 1, read the meta data off the wire
3850 for (int i = 0; i < cColumns; i++) {
3851 // internal meta data class
3852 _SqlMetaData col = altMetaDataSet[i];
3854 if (!stateObj.TryReadByte(out col.op)) {
3855 return false;
3857 if (!stateObj.TryReadUInt16(out col.operand)) {
3858 return false;
3861 // TCE is not applicable to AltMetadata.
3862 if (!TryCommonProcessMetaData(stateObj, col, null, fColMD: false, columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Disabled)) {
3863 return false;
3866 if (ADP.IsEmpty(col.column)) {
3867 // create column name from op
3868 switch (col.op) {
3869 case TdsEnums.AOPAVG:
3870 col.column = "avg";
3871 break;
3873 case TdsEnums.AOPCNT:
3874 col.column = "cnt";
3875 break;
3877 case TdsEnums.AOPCNTB:
3878 col.column = "cntb";
3879 break;
3881 case TdsEnums.AOPMAX:
3882 col.column = "max";
3883 break;
3885 case TdsEnums.AOPMIN:
3886 col.column = "min";
3887 break;
3889 case TdsEnums.AOPSUM:
3890 col.column = "sum";
3891 break;
3893 case TdsEnums.AOPANY:
3894 col.column = "any";
3895 break;
3897 case TdsEnums.AOPNOOP:
3898 col.column = "noop";
3899 break;
3901 case TdsEnums.AOPSTDEV:
3902 col.column = "stdev";
3903 break;
3905 case TdsEnums.AOPSTDEVP:
3906 col.column = "stdevp";
3907 break;
3909 case TdsEnums.AOPVAR:
3910 col.column = "var";
3911 break;
3913 case TdsEnums.AOPVARP:
3914 col.column = "varp";
3915 break;
3918 indexMap[i] = i;
3921 altMetaDataSet.indexMap = indexMap;
3922 altMetaDataSet.visibleColumns = cColumns;
3924 metaData = altMetaDataSet;
3925 return true;
3928 /// <summary>
3929 /// <para> Parses the TDS message to read single CIPHER_INFO entry.</para>
3930 /// </summary>
3931 internal bool TryReadCipherInfoEntry (TdsParserStateObject stateObj, out SqlTceCipherInfoEntry entry) {
3932 byte cekValueCount = 0;
3933 entry = new SqlTceCipherInfoEntry(ordinal: 0);
3935 // Read the DB ID
3936 int dbId;
3937 if (!stateObj.TryReadInt32(out dbId)) {
3938 return false;
3941 // Read the keyID
3942 int keyId;
3943 if (!stateObj.TryReadInt32(out keyId)) {
3944 return false;
3947 // Read the key version
3948 int keyVersion;
3949 if (!stateObj.TryReadInt32(out keyVersion)) {
3950 return false;
3953 // Read the key MD Version
3954 byte[] keyMDVersion = new byte[8];
3955 if (!stateObj.TryReadByteArray(keyMDVersion, 0, 8)) {
3956 return false;
3959 // Read the value count
3960 if (!stateObj.TryReadByte (out cekValueCount)) {
3961 return false;
3964 for (int i = 0; i < cekValueCount; i++) {
3965 // Read individual CEK values
3966 byte[] encryptedCek;
3967 string keyPath;
3968 string keyStoreName;
3969 byte algorithmLength;
3970 string algorithmName;
3971 ushort shortValue;
3972 byte byteValue;
3973 int length;
3975 // Read the length of encrypted CEK
3976 if (!stateObj.TryReadUInt16 (out shortValue)) {
3977 return false;
3980 length = shortValue;
3981 encryptedCek = new byte[length];
3983 // Read the actual encrypted CEK
3984 if (!stateObj.TryReadByteArray (encryptedCek, 0, length)) {
3985 return false;
3988 // Read the length of key store name
3989 if (!stateObj.TryReadByte (out byteValue)) {
3990 return false;
3993 length = byteValue;
3995 // And read the key store name now
3996 if (!stateObj.TryReadString(length, out keyStoreName)) {
3997 return false;
4000 // Read the length of key Path
4001 if (!stateObj.TryReadUInt16 (out shortValue)) {
4002 return false;
4005 length = shortValue;
4007 // Read the key path string
4008 if (!stateObj.TryReadString(length, out keyPath)) {
4009 return false;
4012 // Read the length of the string carrying the encryption algo
4013 if (!stateObj.TryReadByte(out algorithmLength)) {
4014 return false;
4017 length = (int)algorithmLength;
4019 // Read the string carrying the encryption algo (eg. RSA_PKCS_OAEP)
4020 if (!stateObj.TryReadString(length, out algorithmName)) {
4021 return false;
4024 // Add this encrypted CEK blob to our list of encrypted values for the CEK
4025 entry.Add(encryptedCek,
4026 databaseId: dbId,
4027 cekId: keyId,
4028 cekVersion: keyVersion,
4029 cekMdVersion: keyMDVersion,
4030 keyPath: keyPath,
4031 keyStoreName: keyStoreName,
4032 algorithmName: algorithmName);
4035 return true;
4038 /// <summary>
4039 /// <para> Parses the TDS message to read a single CIPHER_INFO table.</para>
4040 /// </summary>
4041 internal bool TryProcessCipherInfoTable (TdsParserStateObject stateObj, out SqlTceCipherInfoTable? cipherTable) {
4042 // Read count
4043 short tableSize = 0;
4044 cipherTable = null;
4045 if (!stateObj.TryReadInt16(out tableSize)) {
4046 return false;
4049 if (0 != tableSize) {
4050 SqlTceCipherInfoTable tempTable = new SqlTceCipherInfoTable(tableSize);
4052 // Read individual entries
4053 for (int i = 0; i < tableSize; i++) {
4054 SqlTceCipherInfoEntry entry;
4055 if (!TryReadCipherInfoEntry (stateObj, out entry)) {
4056 return false;
4059 tempTable[i] = entry;
4062 cipherTable = tempTable;
4065 return true;
4068 internal bool TryProcessMetaData(int cColumns, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData, SqlCommandColumnEncryptionSetting columnEncryptionSetting) {
4069 Debug.Assert(cColumns > 0, "should have at least 1 column in metadata!");
4071 // Read the cipher info table first
4072 SqlTceCipherInfoTable? cipherTable = null;
4073 if (_serverSupportsColumnEncryption) {
4074 if (!TryProcessCipherInfoTable (stateObj, out cipherTable)) {
4075 metaData = null;
4076 return false;
4080 // Read the ColumnData fields
4081 _SqlMetaDataSet newMetaData = new _SqlMetaDataSet(cColumns, cipherTable);
4082 for (int i = 0; i < cColumns; i++) {
4083 if (!TryCommonProcessMetaData(stateObj, newMetaData[i], cipherTable, fColMD: true, columnEncryptionSetting: columnEncryptionSetting)) {
4084 metaData = null;
4085 return false;
4089 // DEVNOTE: cipherTable is discarded at this point since its no longer needed.
4090 metaData = newMetaData;
4091 return true;
4094 private bool IsVarTimeTds(byte tdsType) {
4095 return tdsType == TdsEnums.SQLTIME || tdsType == TdsEnums.SQLDATETIME2 || tdsType == TdsEnums.SQLDATETIMEOFFSET;
4098 private bool TryProcessTypeInfo (TdsParserStateObject stateObj, SqlMetaDataPriv col, UInt32 userType) {
4099 byte byteLen;
4100 byte tdsType;
4101 if (!stateObj.TryReadByte(out tdsType)) {
4102 return false;
4105 if (tdsType == TdsEnums.SQLXMLTYPE)
4106 col.length = TdsEnums.SQL_USHORTVARMAXLEN; //Use the same length as other plp datatypes
4107 else if (IsVarTimeTds(tdsType))
4108 col.length = 0; // placeholder until we read the scale, just make sure it's not SQL_USHORTVARMAXLEN
4109 else if (tdsType == TdsEnums.SQLDATE) {
4110 col.length = 3;
4112 else {
4113 if (!TryGetTokenLength(tdsType, stateObj, out col.length)) {
4114 return false;
4118 col.metaType = MetaType.GetSqlDataType(tdsType, userType, col.length);
4119 col.type = col.metaType.SqlDbType;
4121 // If sphinx, do not change to nullable type
4122 if (_isShiloh)
4123 col.tdsType = (col.isNullable ? col.metaType.NullableType : col.metaType.TDSType);
4124 else
4125 col.tdsType = tdsType;
4127 if (_isYukon) {
4128 if (TdsEnums.SQLUDT == tdsType) {
4129 if (!TryProcessUDTMetaData((SqlMetaDataPriv) col, stateObj)) {
4130 return false;
4134 if (col.length == TdsEnums.SQL_USHORTVARMAXLEN) {
4135 Debug.Assert(tdsType == TdsEnums.SQLXMLTYPE ||
4136 tdsType == TdsEnums.SQLBIGVARCHAR ||
4137 tdsType == TdsEnums.SQLBIGVARBINARY ||
4138 tdsType == TdsEnums.SQLNVARCHAR ||
4139 tdsType == TdsEnums.SQLUDT,
4140 "Invalid streaming datatype");
4141 col.metaType = MetaType.GetMaxMetaTypeFromMetaType(col.metaType);
4142 Debug.Assert(col.metaType.IsLong, "Max datatype not IsLong");
4143 col.length = Int32.MaxValue;
4144 if (tdsType == TdsEnums.SQLXMLTYPE) {
4145 byte schemapresent;
4146 if (!stateObj.TryReadByte(out schemapresent)) {
4147 return false;
4150 if ((schemapresent & 1) != 0) {
4151 if (!stateObj.TryReadByte(out byteLen)) {
4152 return false;
4154 if (byteLen != 0) {
4155 if (!stateObj.TryReadString(byteLen, out col.xmlSchemaCollectionDatabase)) {
4156 return false;
4160 if (!stateObj.TryReadByte(out byteLen)) {
4161 return false;
4163 if (byteLen != 0) {
4164 if (!stateObj.TryReadString(byteLen, out col.xmlSchemaCollectionOwningSchema)) {
4165 return false;
4169 short shortLen;
4170 if (!stateObj.TryReadInt16(out shortLen)) {
4171 return false;
4173 if (byteLen != 0) {
4174 if (!stateObj.TryReadString(shortLen, out col.xmlSchemaCollectionName)) {
4175 return false;
4183 if (col.type == SqlDbType.Decimal) {
4184 if (!stateObj.TryReadByte(out col.precision)) {
4185 return false;
4187 if (!stateObj.TryReadByte(out col.scale)) {
4188 return false;
4192 if (col.metaType.IsVarTime) {
4193 if (!stateObj.TryReadByte(out col.scale)) {
4194 return false;
4197 Debug.Assert(0 <= col.scale && col.scale <= 7);
4199 // calculate actual column length here
4201 switch (col.metaType.SqlDbType)
4203 case SqlDbType.Time:
4204 col.length = MetaType.GetTimeSizeFromScale(col.scale);
4205 break;
4206 case SqlDbType.DateTime2:
4207 // Date in number of days (3 bytes) + time
4208 col.length = 3 + MetaType.GetTimeSizeFromScale(col.scale);
4209 break;
4210 case SqlDbType.DateTimeOffset:
4211 // Date in days (3 bytes) + offset in minutes (2 bytes) + time
4212 col.length = 5 + MetaType.GetTimeSizeFromScale(col.scale);
4213 break;
4215 default:
4216 Debug.Assert(false, "Unknown VariableTime type!");
4217 break;
4221 // read the collation for 7.x servers
4222 if (_isShiloh && col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE)) {
4223 if (!TryProcessCollation(stateObj, out col.collation)) {
4224 return false;
4227 int codePage = GetCodePage(col.collation, stateObj);
4229 if (codePage == _defaultCodePage) {
4230 col.codePage = _defaultCodePage;
4231 col.encoding = _defaultEncoding;
4233 else {
4234 col.codePage = codePage;
4235 col.encoding = System.Text.Encoding.GetEncoding(col.codePage);
4239 return true;
4242 private bool TryCommonProcessMetaData(TdsParserStateObject stateObj, _SqlMetaData col, SqlTceCipherInfoTable? cipherTable, bool fColMD, SqlCommandColumnEncryptionSetting columnEncryptionSetting) {
4243 byte byteLen;
4244 UInt32 userType;
4246 // read user type - 4 bytes Yukon, 2 backwards
4247 if (IsYukonOrNewer) {
4248 if (!stateObj.TryReadUInt32(out userType)) {
4249 return false;
4252 else {
4253 ushort userTypeShort;
4254 if (!stateObj.TryReadUInt16(out userTypeShort)) {
4255 return false;
4257 userType = userTypeShort;
4260 // read flags and set appropriate flags in structure
4261 byte flags;
4262 if (!stateObj.TryReadByte(out flags)) {
4263 return false;
4266 col.updatability = (byte)((flags & TdsEnums.Updatability) >> 2);
4267 col.isNullable = (TdsEnums.Nullable == (flags & TdsEnums.Nullable));
4268 col.isIdentity = (TdsEnums.Identity == (flags & TdsEnums.Identity));
4270 // read second byte of column metadata flags
4271 if (!stateObj.TryReadByte(out flags)) {
4272 return false;
4275 col.isColumnSet = (TdsEnums.IsColumnSet == (flags & TdsEnums.IsColumnSet));
4276 if (fColMD && _serverSupportsColumnEncryption) {
4277 col.isEncrypted = (TdsEnums.IsEncrypted == (flags & TdsEnums.IsEncrypted));
4280 // Read TypeInfo
4281 if (!TryProcessTypeInfo (stateObj, col, userType)) {
4282 return false;
4285 // Read tablename if present
4286 if (col.metaType.IsLong && !col.metaType.IsPlp) {
4287 if (_isYukon) {
4288 int unusedLen = 0xFFFF; //We ignore this value
4289 if (!TryProcessOneTable(stateObj, ref unusedLen, out col.multiPartTableName)) {
4290 return false;
4292 } else {
4293 ushort shortLen;
4294 if (!stateObj.TryReadUInt16(out shortLen)) {
4295 return false;
4297 string tableName;
4298 if (!stateObj.TryReadString(shortLen, out tableName)) {
4299 return false;
4301 // with Sql2000 this is returned as an unquoted mix of catalog.owner.table
4302 // all of which may contain "." and unable to parse correctly from the string alone
4303 // example "select * from pubs..[A.B.C.D.E]" AND only when * will contain a image/text/ntext column
4304 // by delay parsing from execute to SqlDataReader.GetSchemaTable to enable more scenarios
4305 col.multiPartTableName = new MultiPartTableName(tableName);
4309 // Read the TCE column cryptoinfo
4310 if (fColMD && _serverSupportsColumnEncryption && col.isEncrypted) {
4311 // If the column is encrypted, we should have a valid cipherTable
4312 if (cipherTable.HasValue && !TryProcessTceCryptoMetadata (stateObj, col, cipherTable.Value, columnEncryptionSetting, isReturnValue: false)) {
4313 return false;
4317 // Read the column name
4318 if (!stateObj.TryReadByte(out byteLen)) {
4319 return false;
4321 if (!stateObj.TryReadString(byteLen, out col.column)) {
4322 return false;
4325 // We get too many DONE COUNTs from the server, causing too meany StatementCompleted event firings.
4326 // We only need to fire this event when we actually have a meta data stream with 0 or more rows.
4327 stateObj._receivedColMetaData = true;
4328 return true;
4331 private bool TryProcessUDTMetaData(SqlMetaDataPriv metaData, TdsParserStateObject stateObj) {
4332 ushort shortLength;
4333 byte byteLength;
4335 if (!stateObj.TryReadUInt16(out shortLength)) { // max byte size
4336 return false;
4338 metaData.length = shortLength;
4340 // database name
4341 if (!stateObj.TryReadByte(out byteLength)) {
4342 return false;
4344 if (byteLength != 0) {
4345 if (!stateObj.TryReadString(byteLength, out metaData.udtDatabaseName)) {
4346 return false;
4350 // schema name
4351 if (!stateObj.TryReadByte(out byteLength)) {
4352 return false;
4354 if (byteLength != 0) {
4355 if (!stateObj.TryReadString(byteLength, out metaData.udtSchemaName)) {
4356 return false;
4360 // type name
4361 if (!stateObj.TryReadByte(out byteLength)) {
4362 return false;
4364 if (byteLength != 0) {
4365 if (!stateObj.TryReadString(byteLength, out metaData.udtTypeName)) {
4366 return false;
4370 if (!stateObj.TryReadUInt16(out shortLength)) {
4371 return false;
4373 if (shortLength != 0) {
4374 if (!stateObj.TryReadString(shortLength, out metaData.udtAssemblyQualifiedName)) {
4375 return false;
4379 return true;
4382 private void WriteUDTMetaData(object value, string database, string schema, string type,
4383 TdsParserStateObject stateObj) {
4384 // database
4385 if (ADP.IsEmpty(database)) {
4386 stateObj.WriteByte(0);
4388 else {
4389 stateObj.WriteByte((byte)database.Length);
4390 WriteString(database, stateObj);
4393 // schema
4394 if (ADP.IsEmpty(schema)) {
4395 stateObj.WriteByte(0);
4397 else {
4398 stateObj.WriteByte((byte)schema.Length);
4399 WriteString(schema, stateObj);
4402 // type
4403 if (ADP.IsEmpty(type)) {
4404 stateObj.WriteByte(0);
4406 else {
4407 stateObj.WriteByte((byte)type.Length);
4408 WriteString(type, stateObj);
4412 internal bool TryProcessTableName(int length, TdsParserStateObject stateObj, out MultiPartTableName[] multiPartTableNames) {
4413 int tablesAdded = 0;
4415 MultiPartTableName[] tables = new MultiPartTableName[1];
4416 MultiPartTableName mpt;
4417 while (length > 0) {
4421 if (!TryProcessOneTable(stateObj, ref length, out mpt)) {
4422 multiPartTableNames = null;
4423 return false;
4425 if (tablesAdded == 0) {
4426 tables[tablesAdded] = mpt;
4428 else {
4429 MultiPartTableName[] newTables = new MultiPartTableName[tables.Length + 1];
4430 Array.Copy(tables, 0, newTables, 0, tables.Length);
4431 newTables[tables.Length] = mpt;
4432 tables = newTables;
4435 tablesAdded++;
4438 multiPartTableNames = tables;
4439 return true;
4442 private bool TryProcessOneTable(TdsParserStateObject stateObj, ref int length, out MultiPartTableName multiPartTableName) {
4443 ushort tableLen;
4444 MultiPartTableName mpt;
4445 string value;
4447 multiPartTableName = default(MultiPartTableName);
4449 if (_isShilohSP1) {
4451 mpt = new MultiPartTableName();
4452 byte nParts;
4454 // Find out how many parts in the TDS stream
4455 if (!stateObj.TryReadByte(out nParts)) {
4456 return false;
4458 length--;
4459 if (nParts == 4) {
4460 if (!stateObj.TryReadUInt16(out tableLen)) {
4461 return false;
4463 length -= 2;
4464 if (!stateObj.TryReadString(tableLen, out value)) {
4465 return false;
4467 mpt.ServerName = value;
4468 nParts--;
4469 length -= (tableLen * 2); // wide bytes
4471 if (nParts == 3) {
4472 if (!stateObj.TryReadUInt16(out tableLen)) {
4473 return false;
4475 length -= 2;
4476 if (!stateObj.TryReadString(tableLen, out value)) {
4477 return false;
4479 mpt.CatalogName = value;
4480 length -= (tableLen * 2); // wide bytes
4481 nParts--;
4483 if (nParts == 2) {
4484 if (!stateObj.TryReadUInt16(out tableLen)) {
4485 return false;
4487 length -= 2;
4488 if (!stateObj.TryReadString(tableLen, out value)) {
4489 return false;
4491 mpt.SchemaName = value;
4492 length -= (tableLen * 2); // wide bytes
4493 nParts--;
4495 if (nParts == 1) {
4496 if (!stateObj.TryReadUInt16(out tableLen)) {
4497 return false;
4499 length -= 2;
4500 if (!stateObj.TryReadString(tableLen, out value)) {
4501 return false;
4503 mpt.TableName = value;
4504 length -= (tableLen * 2); // wide bytes
4505 nParts--;
4507 Debug.Assert(nParts == 0 , "ProcessTableName:Unidentified parts in the table name token stream!");
4510 else {
4511 if (!stateObj.TryReadUInt16(out tableLen)) {
4512 return false;
4514 length -= 2;
4515 if (!stateObj.TryReadString(tableLen, out value)) {
4516 return false;
4518 string tableName = value;
4519 length -= (tableLen * 2); // wide bytes
4520 mpt = new MultiPartTableName(MultipartIdentifier.ParseMultipartIdentifier(tableName, "[\"", "]\"", Res.SQL_TDSParserTableName, false));
4523 multiPartTableName = mpt;
4524 return true;
4527 // augments current metadata with table and key information
4528 private bool TryProcessColInfo(_SqlMetaDataSet columns, SqlDataReader reader, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData) {
4529 Debug.Assert(columns != null && columns.Length > 0, "no metadata available!");
4531 metaData = null;
4533 for (int i = 0; i < columns.Length; i++) {
4534 _SqlMetaData col = columns[i];
4536 byte ignored;
4537 if (!stateObj.TryReadByte(out ignored)) { // colnum, ignore
4538 return false;
4540 if (!stateObj.TryReadByte(out col.tableNum)) {
4541 return false;
4544 // interpret status
4545 byte status;
4546 if (!stateObj.TryReadByte(out status)) {
4547 return false;
4550 col.isDifferentName = (TdsEnums.SQLDifferentName == (status & TdsEnums.SQLDifferentName));
4551 col.isExpression = (TdsEnums.SQLExpression == (status & TdsEnums.SQLExpression));
4552 col.isKey = (TdsEnums.SQLKey == (status & TdsEnums.SQLKey));
4553 col.isHidden = (TdsEnums.SQLHidden == (status & TdsEnums.SQLHidden));
4555 // read off the base table name if it is different than the select list column name
4556 if (col.isDifferentName) {
4557 byte len;
4558 if (!stateObj.TryReadByte(out len)) {
4559 return false;
4561 if (!stateObj.TryReadString(len, out col.baseColumn)) {
4562 return false;
4566 // Fixup column name - only if result of a table - that is if it was not the result of
4567 // an expression.
4568 if ((reader.TableNames != null) && (col.tableNum > 0)) {
4569 Debug.Assert(reader.TableNames.Length >= col.tableNum, "invalid tableNames array!");
4570 col.multiPartTableName = reader.TableNames[col.tableNum - 1];
4573 // MDAC 60109: expressions are readonly
4574 if (col.isExpression) {
4575 col.updatability = 0;
4579 // set the metadata so that the stream knows some metadata info has changed
4580 metaData = columns;
4581 return true;
4584 // takes care of any per data header information:
4585 // for long columns, reads off textptrs, reads length, check nullability
4586 // for other columns, reads length, checks nullability
4587 // returns length and nullability
4588 internal bool TryProcessColumnHeader(SqlMetaDataPriv col, TdsParserStateObject stateObj, int columnOrdinal, out bool isNull, out ulong length) {
4589 // query NBC row information first
4590 if (stateObj.IsNullCompressionBitSet(columnOrdinal)) {
4591 isNull = true;
4592 // column information is not present in TDS if null compression bit is set, return now
4593 length = 0;
4594 return true;
4597 return TryProcessColumnHeaderNoNBC(col, stateObj, out isNull, out length);
4600 private bool TryProcessColumnHeaderNoNBC(SqlMetaDataPriv col, TdsParserStateObject stateObj, out bool isNull, out ulong length) {
4601 if (col.metaType.IsLong && !col.metaType.IsPlp) {
4603 // we don't care about TextPtrs, simply go after the data after it
4605 byte textPtrLen;
4606 if (!stateObj.TryReadByte(out textPtrLen)) {
4607 isNull = false;
4608 length = 0;
4609 return false;
4612 if (0 != textPtrLen) {
4613 // read past text pointer
4614 if (!stateObj.TrySkipBytes(textPtrLen)) {
4615 isNull = false;
4616 length = 0;
4617 return false;
4620 // read past timestamp
4621 if (!stateObj.TrySkipBytes(TdsEnums.TEXT_TIME_STAMP_LEN)) {
4622 isNull = false;
4623 length = 0;
4624 return false;
4627 isNull = false;
4628 return TryGetDataLength(col, stateObj, out length);
4630 else {
4631 isNull = true;
4632 length = 0;
4633 return true;
4637 else {
4638 // non-blob columns
4639 ulong longlen;
4640 if (!TryGetDataLength(col, stateObj, out longlen)) {
4641 isNull = false;
4642 length = 0;
4643 return false;
4645 isNull = IsNull(col.metaType, longlen);
4646 length = (isNull ? 0 : longlen);
4647 return true;
4651 // assumes that the current position is at the start of an altrow!
4652 internal bool TryGetAltRowId(TdsParserStateObject stateObj, out int id) {
4653 byte token;
4654 if (!stateObj.TryReadByte(out token)) { // skip over ALTROW token
4655 id = 0;
4656 return false;
4658 Debug.Assert((token == TdsEnums.SQLALTROW), "");
4660 // Start a fresh row - disable NBC since Alt Rows are never compressed
4661 if (!stateObj.TryStartNewRow(isNullCompressed: false)) {
4662 id = 0;
4663 return false;
4666 ushort shortId;
4667 if (!stateObj.TryReadUInt16(out shortId)) {
4668 id = 0;
4669 return false;
4672 id = shortId;
4673 return true;
4676 // Used internally by BulkCopy only
4677 private bool TryProcessRow(_SqlMetaDataSet columns, object[] buffer, int[] map, TdsParserStateObject stateObj) {
4678 SqlBuffer data = new SqlBuffer();
4680 for (int i = 0; i < columns.Length; i++) {
4681 _SqlMetaData md = columns[i];
4682 Debug.Assert(md != null, "_SqlMetaData should not be null for column " + i.ToString(CultureInfo.InvariantCulture));
4684 bool isNull;
4685 ulong len;
4686 if (!TryProcessColumnHeader(md, stateObj, i, out isNull, out len)) {
4687 return false;
4690 if (isNull) {
4691 GetNullSqlValue(data, md, SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/, _connHandler);
4692 buffer[map[i]] = data.SqlValue;
4694 else {
4695 // We only read up to 2Gb. Throw if data is larger. Very large data
4696 // should be read in chunks in sequential read mode
4697 // For Plp columns, we may have gotten only the length of the first chunk
4698 if (!TryReadSqlValue(data, md, md.metaType.IsPlp ? (Int32.MaxValue) : (int)len, stateObj,
4699 SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/,
4700 md.column)) {
4701 return false;
4703 buffer[map[i]] = data.SqlValue;
4704 if (stateObj._longlen != 0) {
4705 throw new SqlTruncateException(Res.GetString(Res.SqlMisc_TruncationMaxDataMessage));
4708 data.Clear();
4711 return true;
4714 /// <summary>
4715 /// Determines if a column value should be transparently decrypted (based on SqlCommand and Connection String settings).
4716 /// </summary>
4717 /// <returns>true if the value should be transparently decrypted, false otherwise</returns>
4718 internal static bool ShouldHonorTceForRead (SqlCommandColumnEncryptionSetting columnEncryptionSetting,
4719 SqlInternalConnectionTds connection) {
4721 // Command leve setting trumps all
4722 switch (columnEncryptionSetting) {
4723 case SqlCommandColumnEncryptionSetting.Disabled:
4724 return false;
4725 case SqlCommandColumnEncryptionSetting.Enabled:
4726 return true;
4727 case SqlCommandColumnEncryptionSetting.ResultSetOnly:
4728 return true;
4729 default:
4730 // Check connection level setting!
4731 Debug.Assert(SqlCommandColumnEncryptionSetting.UseConnectionSetting == columnEncryptionSetting,
4732 "Unexpected value for command level override");
4733 return (connection != null && connection.ConnectionOptions != null &&
4734 connection.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled);
4738 internal static object GetNullSqlValue(
4739 SqlBuffer nullVal,
4740 SqlMetaDataPriv md,
4741 SqlCommandColumnEncryptionSetting columnEncryptionSetting,
4742 SqlInternalConnectionTds connection) {
4743 SqlDbType type = md.type;
4745 if (type == SqlDbType.VarBinary && // if its a varbinary
4746 md.isEncrypted &&// and encrypted
4747 ShouldHonorTceForRead(columnEncryptionSetting, connection)){
4748 type = md.baseTI.type; // the use the actual (plaintext) type
4751 switch (type) {
4752 case SqlDbType.Real:
4753 nullVal.SetToNullOfType(SqlBuffer.StorageType.Single);
4754 break;
4756 case SqlDbType.Float:
4757 nullVal.SetToNullOfType(SqlBuffer.StorageType.Double);
4758 break;
4760 case SqlDbType.Udt:
4761 case SqlDbType.Binary:
4762 case SqlDbType.VarBinary:
4763 case SqlDbType.Image:
4764 nullVal.SqlBinary = SqlBinary.Null;
4765 break;
4767 case SqlDbType.UniqueIdentifier:
4768 nullVal.SqlGuid = SqlGuid.Null;
4769 break;
4771 case SqlDbType.Bit:
4772 nullVal.SetToNullOfType(SqlBuffer.StorageType.Boolean);
4773 break;
4775 case SqlDbType.TinyInt:
4776 nullVal.SetToNullOfType(SqlBuffer.StorageType.Byte);
4777 break;
4779 case SqlDbType.SmallInt:
4780 nullVal.SetToNullOfType(SqlBuffer.StorageType.Int16);
4781 break;
4783 case SqlDbType.Int:
4784 nullVal.SetToNullOfType(SqlBuffer.StorageType.Int32);
4785 break;
4787 case SqlDbType.BigInt:
4788 nullVal.SetToNullOfType(SqlBuffer.StorageType.Int64);
4789 break;
4791 case SqlDbType.Char:
4792 case SqlDbType.VarChar:
4793 case SqlDbType.NChar:
4794 case SqlDbType.NVarChar:
4795 case SqlDbType.Text:
4796 case SqlDbType.NText:
4797 nullVal.SetToNullOfType(SqlBuffer.StorageType.String);
4798 break;
4800 case SqlDbType.Decimal:
4801 nullVal.SetToNullOfType(SqlBuffer.StorageType.Decimal);
4802 break;
4804 case SqlDbType.DateTime:
4805 case SqlDbType.SmallDateTime:
4806 nullVal.SetToNullOfType(SqlBuffer.StorageType.DateTime);
4807 break;
4809 case SqlDbType.Money:
4810 case SqlDbType.SmallMoney:
4811 nullVal.SetToNullOfType(SqlBuffer.StorageType.Money);
4812 break;
4814 case SqlDbType.Variant:
4815 // DBNull.Value will have to work here
4816 nullVal.SetToNullOfType(SqlBuffer.StorageType.Empty);
4817 break;
4819 case SqlDbType.Xml:
4820 nullVal.SqlCachedBuffer = SqlCachedBuffer.Null;
4821 break;
4823 case SqlDbType.Date:
4824 nullVal.SetToNullOfType(SqlBuffer.StorageType.Date);
4825 break;
4827 case SqlDbType.Time:
4828 nullVal.SetToNullOfType(SqlBuffer.StorageType.Time);
4829 break;
4831 case SqlDbType.DateTime2:
4832 nullVal.SetToNullOfType(SqlBuffer.StorageType.DateTime2);
4833 break;
4835 case SqlDbType.DateTimeOffset:
4836 nullVal.SetToNullOfType(SqlBuffer.StorageType.DateTimeOffset);
4837 break;
4839 case SqlDbType.Timestamp:
4840 // Dev10 Bug #479607 - this should have been the same as SqlDbType.Binary, but it's a rejected breaking change
4841 // Dev10 Bug #752790 - don't assert when it does happen
4842 break;
4844 default:
4845 Debug.Assert(false, "unknown null sqlType!" + md.type.ToString());
4846 break;
4849 return nullVal;
4852 internal bool TrySkipRow(_SqlMetaDataSet columns, TdsParserStateObject stateObj) {
4853 return TrySkipRow(columns, 0, stateObj);
4856 internal bool TrySkipRow(_SqlMetaDataSet columns, int startCol, TdsParserStateObject stateObj) {
4857 for (int i = startCol; i < columns.Length; i++) {
4858 _SqlMetaData md = columns[i];
4860 if (!TrySkipValue(md, i, stateObj)) {
4861 return false;
4864 return true;
4867 /// <summary>
4868 /// 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
4869 /// </summary>
4870 internal bool TrySkipValue(SqlMetaDataPriv md, int columnOrdinal, TdsParserStateObject stateObj) {
4871 if (stateObj.IsNullCompressionBitSet(columnOrdinal)) {
4872 return true;
4875 if (md.metaType.IsPlp) {
4876 ulong ignored;
4877 if (!TrySkipPlpValue(UInt64.MaxValue, stateObj, out ignored)) {
4878 return false;
4881 else if (md.metaType.IsLong) {
4883 Debug.Assert(!md.metaType.IsPlp, "Plp types must be handled using SkipPlpValue");
4885 byte textPtrLen;
4886 if (!stateObj.TryReadByte(out textPtrLen)) {
4887 return false;
4890 if (0 != textPtrLen) {
4891 if (!stateObj.TrySkipBytes(textPtrLen + TdsEnums.TEXT_TIME_STAMP_LEN)) {
4892 return false;
4895 int length;
4896 if (!TryGetTokenLength(md.tdsType, stateObj, out length)) {
4897 return false;
4899 if (!stateObj.TrySkipBytes(length)) {
4900 return false;
4904 else {
4905 int length;
4906 if (!TryGetTokenLength(md.tdsType, stateObj, out length)) {
4907 return false;
4910 // if false, no value to skip - it's null
4911 if (!IsNull(md.metaType, (ulong)length)) {
4912 if (!stateObj.TrySkipBytes(length)) {
4913 return false;
4918 return true;
4921 private bool IsNull(MetaType mt, ulong length) {
4922 // null bin and char types have a length of -1 to represent null
4923 if (mt.IsPlp) {
4924 return (TdsEnums.SQL_PLP_NULL == length);
4927 // HOTFIX #50000415: for image/text, 0xFFFF is the length, not representing null
4928 if ((TdsEnums.VARNULL == length) && !mt.IsLong) {
4929 return true;
4932 // other types have a length of 0 to represent null
4933 // long and non-PLP types will always return false because these types are either char or binary
4934 // this is expected since for long and non-plp types isnull is checked based on textptr field and not the length
4935 return ((TdsEnums.FIXEDNULL == length) && !mt.IsCharType && !mt.IsBinType);
4938 private bool TryReadSqlStringValue(SqlBuffer value, byte type, int length, Encoding encoding, bool isPlp, TdsParserStateObject stateObj) {
4939 switch (type) {
4940 case TdsEnums.SQLCHAR:
4941 case TdsEnums.SQLBIGCHAR:
4942 case TdsEnums.SQLVARCHAR:
4943 case TdsEnums.SQLBIGVARCHAR:
4944 case TdsEnums.SQLTEXT:
4945 // If bigvarchar(max), we only read the first chunk here,
4946 // expecting the caller to read the rest
4947 if (encoding == null) {
4948 // if hitting 7.0 server, encoding will be null in metadata for columns or return values since
4949 // 7.0 has no support for multiple code pages in data - single code page support only
4950 encoding = _defaultEncoding;
4952 string stringValue;
4953 if (!stateObj.TryReadStringWithEncoding(length, encoding, isPlp, out stringValue)) {
4954 return false;
4956 value.SetToString(stringValue);
4957 break;
4959 case TdsEnums.SQLNCHAR:
4960 case TdsEnums.SQLNVARCHAR:
4961 case TdsEnums.SQLNTEXT:
4963 String s = null;
4965 if (isPlp) {
4966 char[] cc = null;
4968 if (!TryReadPlpUnicodeChars(ref cc, 0, length >> 1, stateObj, out length)) {
4969 return false;
4971 if (length > 0) {
4972 s = new String(cc, 0, length);
4974 else {
4975 s = ADP.StrEmpty;
4978 else {
4979 if (!stateObj.TryReadString(length >> 1, out s)) {
4980 return false;
4984 value.SetToString(s);
4985 break;
4988 default:
4989 Debug.Assert(false, "Unknown tds type for SqlString!" + type.ToString(CultureInfo.InvariantCulture));
4990 break;
4993 return true;
4996 /// <summary>
4997 /// Deserializes the unencrypted bytes into a value based on the target type info.
4998 /// </summary>
4999 internal bool DeserializeUnencryptedValue (SqlBuffer value, byte[] unencryptedBytes, SqlMetaDataPriv md, TdsParserStateObject stateObj, byte normalizationVersion) {
5000 if (normalizationVersion != 0x01) {
5001 throw SQL.UnsupportedNormalizationVersion(normalizationVersion);
5004 byte tdsType = md.baseTI.tdsType;
5005 int length = unencryptedBytes.Length;
5007 // For normalized types, the length and scale of the actual type might be different than the value's.
5008 int denormalizedLength = md.baseTI.length;
5009 byte denormalizedScale = md.baseTI.scale;
5011 Debug.Assert (false == md.baseTI.isEncrypted, "Double encryption detected");
5012 switch (tdsType) {
5013 // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT.
5014 case TdsEnums.SQLBIT:
5015 case TdsEnums.SQLBITN:
5016 case TdsEnums.SQLINTN:
5017 case TdsEnums.SQLINT1:
5018 case TdsEnums.SQLINT2:
5019 case TdsEnums.SQLINT4:
5020 case TdsEnums.SQLINT8:
5021 Debug.Assert(length == 8, "invalid length for SqlInt64 type!");
5022 byte byteValue;
5023 long longValue;
5025 if (unencryptedBytes.Length != 8) {
5026 return false;
5029 longValue = BitConverter.ToInt64(unencryptedBytes, 0);
5031 if (tdsType == TdsEnums.SQLBIT ||
5032 tdsType == TdsEnums.SQLBITN) {
5033 value.Boolean = (longValue != 0);
5034 break;
5037 if (tdsType == TdsEnums.SQLINT1 || denormalizedLength == 1)
5038 value.Byte = (byte)longValue;
5039 else if (tdsType == TdsEnums.SQLINT2 || denormalizedLength == 2)
5040 value.Int16 = (Int16)longValue;
5041 else if (tdsType == TdsEnums.SQLINT4 || denormalizedLength == 4)
5042 value.Int32 = (Int32)longValue;
5043 else
5044 value.Int64 = longValue;
5046 break;
5048 case TdsEnums.SQLFLTN:
5049 if (length == 4) {
5050 goto case TdsEnums.SQLFLT4;
5052 else {
5053 goto case TdsEnums.SQLFLT8;
5056 case TdsEnums.SQLFLT4:
5057 Debug.Assert(length == 4, "invalid length for SqlSingle type!");
5058 float singleValue;
5059 if (unencryptedBytes.Length != 4) {
5060 return false;
5063 singleValue = BitConverter.ToSingle(unencryptedBytes, 0);
5064 value.Single = singleValue;
5065 break;
5067 case TdsEnums.SQLFLT8:
5068 double doubleValue;
5069 if (unencryptedBytes.Length != 8) {
5070 return false;
5073 doubleValue = BitConverter.ToDouble(unencryptedBytes, 0);
5074 value.Double = doubleValue;
5075 break;
5077 // We normalize to allow conversion across data types. SMALLMONEY is serialized into a MONEY.
5078 case TdsEnums.SQLMONEYN:
5079 case TdsEnums.SQLMONEY4:
5080 case TdsEnums.SQLMONEY:
5082 int mid;
5083 uint lo;
5085 if (unencryptedBytes.Length != 8) {
5086 return false;
5089 mid = BitConverter.ToInt32(unencryptedBytes, 0);
5090 lo = BitConverter.ToUInt32(unencryptedBytes, 4);
5092 long l = (((long)mid) << 0x20) + ((long)lo);
5093 value.SetToMoney(l);
5094 break;
5097 case TdsEnums.SQLDATETIMN:
5098 if (length == 4) {
5099 goto case TdsEnums.SQLDATETIM4;
5101 else {
5102 goto case TdsEnums.SQLDATETIME;
5105 case TdsEnums.SQLDATETIM4:
5106 ushort daypartShort, timepartShort;
5107 if (unencryptedBytes.Length != 4) {
5108 return false;
5111 daypartShort = (UInt16)((unencryptedBytes[1] << 8) + unencryptedBytes[0]);
5112 timepartShort = (UInt16)((unencryptedBytes[3] << 8) + unencryptedBytes[2]);
5113 value.SetToDateTime(daypartShort, timepartShort * SqlDateTime.SQLTicksPerMinute);
5114 break;
5116 case TdsEnums.SQLDATETIME:
5117 int daypart;
5118 uint timepart;
5119 if (unencryptedBytes.Length != 8) {
5120 return false;
5123 daypart = BitConverter.ToInt32(unencryptedBytes, 0);
5124 timepart = BitConverter.ToUInt32(unencryptedBytes, 4);
5125 value.SetToDateTime(daypart, (int)timepart);
5126 break;
5128 case TdsEnums.SQLUNIQUEID:
5130 Debug.Assert(length == 16, "invalid length for SqlGuid type!");
5131 value.SqlGuid = new SqlGuid(unencryptedBytes, true); // doesn't copy the byte array
5132 break;
5135 case TdsEnums.SQLBINARY:
5136 case TdsEnums.SQLBIGBINARY:
5137 case TdsEnums.SQLBIGVARBINARY:
5138 case TdsEnums.SQLVARBINARY:
5139 case TdsEnums.SQLIMAGE:
5141 // Note: Better not come here with plp data!!
5142 Debug.Assert(length <= TdsEnums.MAXSIZE, "Plp data decryption attempted");
5144 // If this is a fixed length type, pad with zeros to get to the fixed length size.
5145 if (tdsType == TdsEnums.SQLBINARY || tdsType == TdsEnums.SQLBIGBINARY) {
5146 byte[] bytes = new byte[md.baseTI.length];
5147 Buffer.BlockCopy(unencryptedBytes, 0, bytes, 0, unencryptedBytes.Length);
5148 unencryptedBytes = bytes;
5151 value.SqlBinary = new SqlBinary(unencryptedBytes, true); // doesn't copy the byte array
5152 break;
5155 case TdsEnums.SQLDECIMALN:
5156 case TdsEnums.SQLNUMERICN:
5157 // Check the sign from the first byte.
5158 int index = 0;
5159 byteValue = unencryptedBytes[index++];
5160 bool fPositive = (1 == byteValue);
5162 // Now read the 4 next integers which contain the actual value.
5163 length = checked((int)length-1);
5164 int[] bits = new int[4];
5165 int decLength = length>>2;
5166 for (int i = 0; i < decLength; i++) {
5167 // up to 16 bytes of data following the sign byte
5168 bits[i] = BitConverter.ToInt32(unencryptedBytes, index);
5169 index += 4;
5171 value.SetToDecimal (md.baseTI.precision, md.baseTI.scale, fPositive, bits);
5172 break;
5174 case TdsEnums.SQLCHAR:
5175 case TdsEnums.SQLBIGCHAR:
5176 case TdsEnums.SQLVARCHAR:
5177 case TdsEnums.SQLBIGVARCHAR:
5178 case TdsEnums.SQLTEXT:
5180 System.Text.Encoding encoding = md.baseTI.encoding;
5182 if (null == encoding) {
5183 encoding = _defaultEncoding;
5186 if (null == encoding) {
5187 ThrowUnsupportedCollationEncountered(stateObj);
5190 string strValue = encoding.GetString(unencryptedBytes, 0, length);
5192 // If this is a fixed length type, pad with spaces to get to the fixed length size.
5193 if (tdsType == TdsEnums.SQLCHAR || tdsType == TdsEnums.SQLBIGCHAR) {
5194 strValue = strValue.PadRight(md.baseTI.length);
5197 value.SetToString(strValue);
5198 break;
5201 case TdsEnums.SQLNCHAR:
5202 case TdsEnums.SQLNVARCHAR:
5203 case TdsEnums.SQLNTEXT:
5205 string strValue = System.Text.Encoding.Unicode.GetString(unencryptedBytes, 0, length);
5207 // If this is a fixed length type, pad with spaces to get to the fixed length size.
5208 if (tdsType == TdsEnums.SQLNCHAR) {
5209 strValue = strValue.PadRight(md.baseTI.length / ADP.CharSize);
5212 value.SetToString(strValue);
5213 break;
5216 case TdsEnums.SQLDATE:
5217 Debug.Assert(length == 3, "invalid length for date type!");
5218 value.SetToDate(unencryptedBytes);
5219 break;
5221 case TdsEnums.SQLTIME:
5222 // We normalize to maximum precision to allow conversion across different precisions.
5223 Debug.Assert(length == 5, "invalid length for time type!");
5224 value.SetToTime(unencryptedBytes, length, TdsEnums.MAX_TIME_SCALE, denormalizedScale);
5225 break;
5227 case TdsEnums.SQLDATETIME2:
5228 // We normalize to maximum precision to allow conversion across different precisions.
5229 Debug.Assert(length == 8, "invalid length for datetime2 type!");
5230 value.SetToDateTime2(unencryptedBytes, length, TdsEnums.MAX_TIME_SCALE, denormalizedScale);
5231 break;
5233 case TdsEnums.SQLDATETIMEOFFSET:
5234 // We normalize to maximum precision to allow conversion across different precisions.
5235 Debug.Assert(length == 10, "invalid length for datetimeoffset type!");
5236 value.SetToDateTimeOffset(unencryptedBytes, length, TdsEnums.MAX_TIME_SCALE, denormalizedScale);
5237 break;
5239 default:
5240 MetaType metaType = md.baseTI.metaType;
5242 // If we don't have a metatype already, construct one to get the proper type name.
5243 if (metaType == null) {
5244 metaType = MetaType.GetSqlDataType(tdsType, userType:0, length:length);
5247 throw SQL.UnsupportedDatatypeEncryption(metaType.TypeName);
5250 return true;
5253 internal bool TryReadSqlValue(SqlBuffer value,
5254 SqlMetaDataPriv md,
5255 int length,
5256 TdsParserStateObject stateObj,
5257 SqlCommandColumnEncryptionSetting columnEncryptionOverride,
5258 string columnName) {
5259 bool isPlp = md.metaType.IsPlp;
5260 byte tdsType = md.tdsType;
5262 Debug.Assert(isPlp || !IsNull(md.metaType, (ulong)length), "null value should not get here!");
5263 if (isPlp) {
5264 // We must read the column value completely, no matter what length is passed in
5265 length = Int32.MaxValue;
5268 //DEVNOTE: When modifying the following routines (for deserialization) please pay attention to
5269 // deserialization code in DecryptWithKey () method and modify it accordingly.
5270 switch (tdsType) {
5271 case TdsEnums.SQLDECIMALN:
5272 case TdsEnums.SQLNUMERICN:
5273 if (!TryReadSqlDecimal(value, length, md.precision, md.scale, stateObj)) {
5274 return false;
5276 break;
5278 case TdsEnums.SQLUDT:
5279 case TdsEnums.SQLBINARY:
5280 case TdsEnums.SQLBIGBINARY:
5281 case TdsEnums.SQLBIGVARBINARY:
5282 case TdsEnums.SQLVARBINARY:
5283 case TdsEnums.SQLIMAGE:
5284 byte[] b = null;
5286 // If varbinary(max), we only read the first chunk here, expecting the caller to read the rest
5287 if (isPlp) {
5288 // If we are given -1 for length, then we read the entire value,
5289 // otherwise only the requested amount, usually first chunk.
5290 int ignored;
5291 if (!stateObj.TryReadPlpBytes(ref b, 0, length, out ignored)) {
5292 return false;
5295 else {
5296 //Debug.Assert(length > 0 && length < (long)(Int32.MaxValue), "Bad length for column");
5297 b = new byte[length];
5298 if (!stateObj.TryReadByteArray(b, 0, length)) {
5299 return false;
5303 if (md.isEncrypted
5304 && ((columnEncryptionOverride == SqlCommandColumnEncryptionSetting.Enabled
5305 || columnEncryptionOverride == SqlCommandColumnEncryptionSetting.ResultSetOnly)
5306 || (columnEncryptionOverride == SqlCommandColumnEncryptionSetting.UseConnectionSetting
5307 && _connHandler != null && _connHandler.ConnectionOptions != null
5308 && _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled))) {
5309 try {
5310 // CipherInfo is present, decrypt and read
5311 byte[] unencryptedBytes = SqlSecurityUtility.DecryptWithKey(b, md.cipherMD, _connHandler.ConnectionOptions.DataSource);
5313 if (unencryptedBytes != null) {
5314 DeserializeUnencryptedValue(value, unencryptedBytes, md, stateObj, md.NormalizationRuleVersion);
5317 catch (Exception e) {
5318 throw SQL.ColumnDecryptionFailed(columnName, null, e);
5321 else {
5322 value.SqlBinary = new SqlBinary(b, true); // doesn't copy the byte array
5324 break;
5326 case TdsEnums.SQLCHAR:
5327 case TdsEnums.SQLBIGCHAR:
5328 case TdsEnums.SQLVARCHAR:
5329 case TdsEnums.SQLBIGVARCHAR:
5330 case TdsEnums.SQLTEXT:
5331 case TdsEnums.SQLNCHAR:
5332 case TdsEnums.SQLNVARCHAR:
5333 case TdsEnums.SQLNTEXT:
5334 if (!TryReadSqlStringValue(value, tdsType, length, md.encoding, isPlp, stateObj)) {
5335 return false;
5337 break;
5339 case TdsEnums.SQLXMLTYPE:
5340 // We store SqlCachedBuffer here, so that we can return either SqlBinary, SqlString or SqlXmlReader.
5341 SqlCachedBuffer sqlBuf;
5342 if (!SqlCachedBuffer.TryCreate(md, this, stateObj, out sqlBuf)) {
5343 return false;
5346 value.SqlCachedBuffer = sqlBuf;
5347 break;
5349 case TdsEnums.SQLDATE:
5350 case TdsEnums.SQLTIME:
5351 case TdsEnums.SQLDATETIME2:
5352 case TdsEnums.SQLDATETIMEOFFSET:
5353 if (!TryReadSqlDateTime(value, tdsType, length, md.scale, stateObj)) {
5354 return false;
5356 break;
5358 default:
5359 Debug.Assert(!isPlp, "ReadSqlValue calling ReadSqlValueInternal with plp data");
5360 if (!TryReadSqlValueInternal(value, tdsType, length, stateObj)) {
5361 return false;
5363 break;
5366 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));
5367 return true;
5370 private bool TryReadSqlDateTime(SqlBuffer value, byte tdsType, int length, byte scale, TdsParserStateObject stateObj) {
5371 byte[] datetimeBuffer = new byte[length];
5373 if (!stateObj.TryReadByteArray(datetimeBuffer, 0, length)) {
5374 return false;
5377 switch (tdsType) {
5378 case TdsEnums.SQLDATE:
5379 Debug.Assert(length == 3, "invalid length for date type!");
5380 value.SetToDate(datetimeBuffer);
5381 break;
5383 case TdsEnums.SQLTIME:
5384 Debug.Assert(3 <= length && length <= 5, "invalid length for time type!");
5385 value.SetToTime(datetimeBuffer, length, scale, scale);
5386 break;
5388 case TdsEnums.SQLDATETIME2:
5389 Debug.Assert(6 <= length && length <= 8, "invalid length for datetime2 type!");
5390 value.SetToDateTime2(datetimeBuffer, length, scale, scale);
5391 break;
5393 case TdsEnums.SQLDATETIMEOFFSET:
5394 Debug.Assert(8 <= length && length <= 10, "invalid length for datetimeoffset type!");
5395 value.SetToDateTimeOffset(datetimeBuffer, length, scale, scale);
5396 break;
5398 default:
5399 Debug.Assert(false, "ReadSqlDateTime is called with the wrong tdsType");
5400 break;
5403 return true;
5406 internal bool TryReadSqlValueInternal(SqlBuffer value, byte tdsType, int length, TdsParserStateObject stateObj) {
5407 switch (tdsType) {
5408 case TdsEnums.SQLBIT:
5409 case TdsEnums.SQLBITN:
5410 Debug.Assert(length == 1, "invalid length for SqlBoolean type!");
5411 byte byteValue;
5412 if (!stateObj.TryReadByte(out byteValue)) {
5413 return false;
5415 value.Boolean = (byteValue != 0);
5416 break;
5418 case TdsEnums.SQLINTN:
5419 if (length == 1) {
5420 goto case TdsEnums.SQLINT1;
5422 else if (length == 2) {
5423 goto case TdsEnums.SQLINT2;
5425 else if (length == 4) {
5426 goto case TdsEnums.SQLINT4;
5428 else {
5429 goto case TdsEnums.SQLINT8;
5432 case TdsEnums.SQLINT1:
5433 Debug.Assert(length == 1, "invalid length for SqlByte type!");
5434 if (!stateObj.TryReadByte(out byteValue)) {
5435 return false;
5437 value.Byte = byteValue;
5438 break;
5440 case TdsEnums.SQLINT2:
5441 Debug.Assert(length == 2, "invalid length for SqlInt16 type!");
5442 short shortValue;
5443 if (!stateObj.TryReadInt16(out shortValue)) {
5444 return false;
5446 value.Int16 = shortValue;
5447 break;
5449 case TdsEnums.SQLINT4:
5450 Debug.Assert(length == 4, "invalid length for SqlInt32 type!");
5451 int intValue;
5452 if (!stateObj.TryReadInt32(out intValue)) {
5453 return false;
5455 value.Int32 = intValue;
5456 break;
5458 case TdsEnums.SQLINT8:
5459 Debug.Assert(length == 8, "invalid length for SqlInt64 type!");
5460 long longValue;
5461 if (!stateObj.TryReadInt64(out longValue)) {
5462 return false;
5464 value.Int64 = longValue;
5465 break;
5467 case TdsEnums.SQLFLTN:
5468 if (length == 4) {
5469 goto case TdsEnums.SQLFLT4;
5471 else {
5472 goto case TdsEnums.SQLFLT8;
5475 case TdsEnums.SQLFLT4:
5476 Debug.Assert(length == 4, "invalid length for SqlSingle type!");
5477 float singleValue;
5478 if (!stateObj.TryReadSingle(out singleValue)) {
5479 return false;
5481 value.Single = singleValue;
5482 break;
5484 case TdsEnums.SQLFLT8:
5485 Debug.Assert(length == 8, "invalid length for SqlDouble type!");
5486 double doubleValue;
5487 if (!stateObj.TryReadDouble(out doubleValue)) {
5488 return false;
5490 value.Double = doubleValue;
5491 break;
5493 case TdsEnums.SQLMONEYN:
5494 if (length == 4) {
5495 goto case TdsEnums.SQLMONEY4;
5497 else {
5498 goto case TdsEnums.SQLMONEY;
5501 case TdsEnums.SQLMONEY:
5503 int mid;
5504 uint lo;
5506 if (!stateObj.TryReadInt32(out mid)) {
5507 return false;
5509 if (!stateObj.TryReadUInt32(out lo)) {
5510 return false;
5513 long l = (((long)mid) << 0x20) + ((long)lo);
5515 value.SetToMoney(l);
5516 break;
5519 case TdsEnums.SQLMONEY4:
5520 if (!stateObj.TryReadInt32(out intValue)) {
5521 return false;
5523 value.SetToMoney(intValue);
5524 break;
5526 case TdsEnums.SQLDATETIMN:
5527 if (length == 4) {
5528 goto case TdsEnums.SQLDATETIM4;
5530 else {
5531 goto case TdsEnums.SQLDATETIME;
5534 case TdsEnums.SQLDATETIM4:
5535 ushort daypartShort, timepartShort;
5536 if (!stateObj.TryReadUInt16(out daypartShort)) {
5537 return false;
5539 if (!stateObj.TryReadUInt16(out timepartShort)) {
5540 return false;
5542 value.SetToDateTime(daypartShort, timepartShort * SqlDateTime.SQLTicksPerMinute);
5543 break;
5545 case TdsEnums.SQLDATETIME:
5546 int daypart;
5547 uint timepart;
5548 if (!stateObj.TryReadInt32(out daypart)) {
5549 return false;
5551 if (!stateObj.TryReadUInt32(out timepart)) {
5552 return false;
5554 value.SetToDateTime(daypart, (int)timepart);
5555 break;
5557 case TdsEnums.SQLUNIQUEID:
5559 Debug.Assert(length == 16, "invalid length for SqlGuid type!");
5561 byte[] b = new byte[length];
5563 if (!stateObj.TryReadByteArray(b, 0, length)) {
5564 return false;
5566 value.SqlGuid = new SqlGuid(b, true); // doesn't copy the byte array
5567 break;
5570 case TdsEnums.SQLBINARY:
5571 case TdsEnums.SQLBIGBINARY:
5572 case TdsEnums.SQLBIGVARBINARY:
5573 case TdsEnums.SQLVARBINARY:
5574 case TdsEnums.SQLIMAGE:
5576 // Note: Better not come here with plp data!!
5577 Debug.Assert(length <= TdsEnums.MAXSIZE);
5578 byte[] b = new byte[length];
5579 if (!stateObj.TryReadByteArray(b, 0, length)) {
5580 return false;
5582 value.SqlBinary = new SqlBinary(b, true); // doesn't copy the byte array
5584 break;
5587 case TdsEnums.SQLVARIANT:
5588 if (!TryReadSqlVariant(value, length, stateObj)) {
5589 return false;
5591 break;
5593 default:
5594 Debug.Assert(false, "Unknown SqlType!" + tdsType.ToString(CultureInfo.InvariantCulture));
5595 break;
5596 } // switch
5598 return true;
5602 // Read in a SQLVariant
5604 // SQLVariant looks like:
5605 // struct
5606 // {
5607 // BYTE TypeTag
5608 // BYTE cbPropBytes
5609 // BYTE[] Properties
5610 // BYTE[] DataVal
5611 // }
5612 internal bool TryReadSqlVariant(SqlBuffer value, int lenTotal, TdsParserStateObject stateObj) {
5613 Debug.Assert(_isShiloh == true, "Shouldn't be dealing with sql_variaint in pre-SQL2000 server!");
5614 // get the SQLVariant type
5615 byte type;
5616 if (!stateObj.TryReadByte(out type)) {
5617 return false;
5619 ushort lenMax = 0; // maximum lenData of value inside variant
5621 // read cbPropBytes
5622 byte cbPropsActual;
5623 if (!stateObj.TryReadByte(out cbPropsActual)) {
5624 return false;
5626 MetaType mt = MetaType.GetSqlDataType(type, 0 /*no user datatype*/, 0 /* no lenData, non-nullable type */);
5627 byte cbPropsExpected = mt.PropBytes;
5629 int lenConsumed = TdsEnums.SQLVARIANT_SIZE + cbPropsActual; // type, count of propBytes, and actual propBytes
5630 int lenData = lenTotal - lenConsumed; // length of actual data
5632 // read known properties and skip unknown properties
5633 Debug.Assert(cbPropsActual >= cbPropsExpected, "cbPropsActual is less that cbPropsExpected!");
5636 // now read the value
5638 switch (type) {
5639 case TdsEnums.SQLBIT:
5640 case TdsEnums.SQLINT1:
5641 case TdsEnums.SQLINT2:
5642 case TdsEnums.SQLINT4:
5643 case TdsEnums.SQLINT8:
5644 case TdsEnums.SQLFLT4:
5645 case TdsEnums.SQLFLT8:
5646 case TdsEnums.SQLMONEY:
5647 case TdsEnums.SQLMONEY4:
5648 case TdsEnums.SQLDATETIME:
5649 case TdsEnums.SQLDATETIM4:
5650 case TdsEnums.SQLUNIQUEID:
5651 if (!TryReadSqlValueInternal(value, type, lenData, stateObj)) {
5652 return false;
5654 break;
5656 case TdsEnums.SQLDECIMALN:
5657 case TdsEnums.SQLNUMERICN:
5659 Debug.Assert(cbPropsExpected == 2, "SqlVariant: invalid PropBytes for decimal/numeric type!");
5661 byte precision;
5662 if (!stateObj.TryReadByte(out precision)) {
5663 return false;
5665 byte scale;
5666 if (!stateObj.TryReadByte(out scale)) {
5667 return false;
5670 // skip over unknown properties
5671 if (cbPropsActual > cbPropsExpected) {
5672 if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) {
5673 return false;
5677 if (!TryReadSqlDecimal(value, TdsEnums.MAX_NUMERIC_LEN, precision, scale, stateObj)) {
5678 return false;
5680 break;
5683 case TdsEnums.SQLBIGBINARY:
5684 case TdsEnums.SQLBIGVARBINARY:
5685 //Debug.Assert(TdsEnums.VARNULL == lenData, "SqlVariant: data length for Binary indicates null?");
5686 Debug.Assert(cbPropsExpected == 2, "SqlVariant: invalid PropBytes for binary type!");
5688 if (!stateObj.TryReadUInt16(out lenMax)) {
5689 return false;
5691 Debug.Assert(lenMax != TdsEnums.SQL_USHORTVARMAXLEN, "bigvarbinary(max) in a sqlvariant");
5693 // skip over unknown properties
5694 if (cbPropsActual > cbPropsExpected) {
5695 if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) {
5696 return false;
5700 goto case TdsEnums.SQLBIT;
5702 case TdsEnums.SQLBIGCHAR:
5703 case TdsEnums.SQLBIGVARCHAR:
5704 case TdsEnums.SQLNCHAR:
5705 case TdsEnums.SQLNVARCHAR:
5707 Debug.Assert(cbPropsExpected == 7, "SqlVariant: invalid PropBytes for character type!");
5709 SqlCollation collation;
5710 if (!TryProcessCollation(stateObj, out collation)) {
5711 return false;
5714 if (!stateObj.TryReadUInt16(out lenMax)) {
5715 return false;
5717 Debug.Assert(lenMax != TdsEnums.SQL_USHORTVARMAXLEN, "bigvarchar(max) or nvarchar(max) in a sqlvariant");
5719 // skip over unknown properties
5720 if (cbPropsActual > cbPropsExpected) {
5721 if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) {
5722 return false;
5726 Encoding encoding = Encoding.GetEncoding(GetCodePage(collation, stateObj));
5727 if (!TryReadSqlStringValue(value, type, lenData, encoding, false, stateObj)) {
5728 return false;
5730 break;
5732 case TdsEnums.SQLDATE:
5733 if (!TryReadSqlDateTime(value, type, lenData, 0, stateObj)) {
5734 return false;
5736 break;
5738 case TdsEnums.SQLTIME:
5739 case TdsEnums.SQLDATETIME2:
5740 case TdsEnums.SQLDATETIMEOFFSET:
5742 Debug.Assert(cbPropsExpected == 1, "SqlVariant: invalid PropBytes for time/datetime2/datetimeoffset type!");
5744 byte scale;
5745 if (!stateObj.TryReadByte(out scale)) {
5746 return false;
5749 // skip over unknown properties
5750 if (cbPropsActual > cbPropsExpected) {
5751 if (!stateObj.TrySkipBytes(cbPropsActual - cbPropsExpected)) {
5752 return false;
5756 if (!TryReadSqlDateTime(value, type, lenData, scale, stateObj)) {
5757 return false;
5759 break;
5762 default:
5763 Debug.Assert(false, "Unknown tds type in SqlVariant!" + type.ToString(CultureInfo.InvariantCulture));
5764 break;
5765 } // switch
5767 return true;
5771 // Translates a com+ object -> SqlVariant
5772 // when the type is ambiguous, we always convert to the bigger type
5773 // note that we also write out the maxlen and actuallen members (4 bytes each)
5774 // in addition to the SQLVariant structure
5776 internal Task WriteSqlVariantValue(object value, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) {
5777 Debug.Assert(_isShiloh == true, "Shouldn't be dealing with sql_variant in pre-SQL2000 server!");
5779 // handle null values
5780 if (ADP.IsNull(value)) {
5781 WriteInt(TdsEnums.FIXEDNULL, stateObj); //maxlen
5782 WriteInt(TdsEnums.FIXEDNULL, stateObj); //actuallen
5783 return null;
5786 MetaType mt = MetaType.GetMetaTypeFromValue(value);
5788 // Special case data type correction for SqlMoney inside a SqlVariant.
5789 if ((TdsEnums.SQLNUMERICN == mt.TDSType) && (8 == length)) {
5790 // The caller will coerce all SqlTypes to native CLR types, which means SqlMoney will
5791 // coerce to decimal/SQLNUMERICN (via SqlMoney.Value call). In the case where the original
5792 // value was SqlMoney the caller will also pass in the metadata length for the SqlMoney type
5793 // which is 8 bytes. To honor the intent of the caller here we coerce this special case
5794 // input back to SqlMoney from decimal/SQLNUMERICN.
5795 mt = MetaType.GetMetaTypeFromValue(new SqlMoney((decimal)value));
5798 if (mt.IsAnsiType) {
5799 length = GetEncodingCharLength((string)value, length, 0, _defaultEncoding);
5802 // max and actual len are equal to
5803 // SQLVARIANTSIZE {type (1 byte) + cbPropBytes (1 byte)} + cbPropBytes + length (actual length of data in bytes)
5804 WriteInt(TdsEnums.SQLVARIANT_SIZE + mt.PropBytes + length, stateObj); // maxLen
5805 WriteInt(TdsEnums.SQLVARIANT_SIZE + mt.PropBytes + length, stateObj); // actualLen
5807 // write the SQLVariant header (type and cbPropBytes)
5808 stateObj.WriteByte(mt.TDSType);
5809 stateObj.WriteByte(mt.PropBytes);
5811 // now write the actual PropBytes and data
5812 switch (mt.TDSType) {
5813 case TdsEnums.SQLFLT4:
5814 WriteFloat((Single)value, stateObj);
5815 break;
5817 case TdsEnums.SQLFLT8:
5818 WriteDouble((Double)value, stateObj);
5819 break;
5821 case TdsEnums.SQLINT8:
5822 WriteLong((Int64)value, stateObj);
5823 break;
5825 case TdsEnums.SQLINT4:
5826 WriteInt((Int32)value, stateObj);
5827 break;
5829 case TdsEnums.SQLINT2:
5830 WriteShort((Int16)value, stateObj);
5831 break;
5833 case TdsEnums.SQLINT1:
5834 stateObj.WriteByte((byte)value);
5835 break;
5837 case TdsEnums.SQLBIT:
5838 if ((bool)value == true)
5839 stateObj.WriteByte(1);
5840 else
5841 stateObj.WriteByte(0);
5843 break;
5845 case TdsEnums.SQLBIGVARBINARY:
5847 byte[] b = (byte[])value;
5849 WriteShort(length, stateObj); // propbytes: varlen
5850 return stateObj.WriteByteArray(b, length, offset, canAccumulate);
5853 case TdsEnums.SQLBIGVARCHAR:
5855 string s = (string)value;
5857 WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info
5858 stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId
5859 WriteShort(length, stateObj); // propbyte: varlen
5860 return WriteEncodingChar(s, _defaultEncoding, stateObj, canAccumulate);
5863 case TdsEnums.SQLUNIQUEID:
5865 System.Guid guid = (System.Guid)value;
5866 byte[] b = guid.ToByteArray();
5868 Debug.Assert((length == b.Length) && (length == 16), "Invalid length for guid type in com+ object");
5869 stateObj.WriteByteArray(b, length, 0);
5870 break;
5873 case TdsEnums.SQLNVARCHAR:
5875 string s = (string)value;
5877 WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info
5878 stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId
5879 WriteShort(length, stateObj); // propbyte: varlen
5881 // string takes cchar, not cbyte so convert
5882 length >>= 1;
5883 return WriteString(s, length, offset, stateObj, canAccumulate);
5886 case TdsEnums.SQLDATETIME:
5888 TdsDateTime dt = MetaType.FromDateTime((DateTime)value, 8);
5890 WriteInt(dt.days, stateObj);
5891 WriteInt(dt.time, stateObj);
5892 break;
5895 case TdsEnums.SQLMONEY:
5897 WriteCurrency((Decimal)value, 8, stateObj);
5898 break;
5901 case TdsEnums.SQLNUMERICN:
5903 stateObj.WriteByte(mt.Precision); //propbytes: precision
5904 stateObj.WriteByte((byte)((Decimal.GetBits((Decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale
5905 WriteDecimal((Decimal)value, stateObj);
5906 break;
5909 case TdsEnums.SQLTIME:
5910 stateObj.WriteByte(mt.Scale); //propbytes: scale
5911 WriteTime((TimeSpan)value, mt.Scale, length, stateObj);
5912 break;
5914 case TdsEnums.SQLDATETIMEOFFSET:
5915 stateObj.WriteByte(mt.Scale); //propbytes: scale
5916 WriteDateTimeOffset((DateTimeOffset)value, mt.Scale, length, stateObj);
5917 break;
5919 default:
5920 Debug.Assert(false, "unknown tds type for sqlvariant!");
5921 break;
5922 } // switch
5923 // return point for accumulated writes, note: non-accumulated writes returned from their case statements
5924 return null;
5927 // todo: since we now know the difference between SqlWriteVariantValue and SqlWriteRowDataVariant we should consider
5928 // combining these tow methods.
5931 // Translates a com+ object -> SqlVariant
5932 // when the type is ambiguous, we always convert to the bigger type
5933 // note that we also write out the maxlen and actuallen members (4 bytes each)
5934 // in addition to the SQLVariant structure
5936 // Devnote: DataRows are preceeded by Metadata. The Metadata includes the MaxLen value.
5937 // Therefore the sql_variant value must not include the MaxLength. This is the major difference
5938 // between this method and WriteSqlVariantValue above.
5940 internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject stateObj, bool canAccumulate = true) {
5941 Debug.Assert(_isShiloh == true, "Shouldn't be dealing with sql_variant in pre-SQL2000 server!");
5943 // handle null values
5944 if ((null == value) || (DBNull.Value == value)) {
5945 WriteInt(TdsEnums.FIXEDNULL, stateObj);
5946 return null;
5949 MetaType metatype = MetaType.GetMetaTypeFromValue(value);
5950 int length = 0;
5952 if (metatype.IsAnsiType) {
5953 length = GetEncodingCharLength((string)value, length, 0, _defaultEncoding);
5956 switch (metatype.TDSType) {
5957 case TdsEnums.SQLFLT4:
5958 WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj);
5959 WriteFloat((Single)value, stateObj);
5960 break;
5962 case TdsEnums.SQLFLT8:
5963 WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj);
5964 WriteDouble((Double)value, stateObj);
5965 break;
5967 case TdsEnums.SQLINT8:
5968 WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj);
5969 WriteLong((Int64)value, stateObj);
5970 break;
5972 case TdsEnums.SQLINT4:
5973 WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj);
5974 WriteInt((Int32)value, stateObj);
5975 break;
5977 case TdsEnums.SQLINT2:
5978 WriteSqlVariantHeader(4, metatype.TDSType, metatype.PropBytes, stateObj);
5979 WriteShort((Int16)value, stateObj);
5980 break;
5982 case TdsEnums.SQLINT1:
5983 WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj);
5984 stateObj.WriteByte((byte)value);
5985 break;
5987 case TdsEnums.SQLBIT:
5988 WriteSqlVariantHeader(3, metatype.TDSType, metatype.PropBytes, stateObj);
5989 if ((bool)value == true)
5990 stateObj.WriteByte(1);
5991 else
5992 stateObj.WriteByte(0);
5994 break;
5996 case TdsEnums.SQLBIGVARBINARY:
5998 byte[] b = (byte[])value;
6000 length = b.Length;
6001 WriteSqlVariantHeader(4 + length, metatype.TDSType, metatype.PropBytes, stateObj);
6002 WriteShort(length, stateObj); // propbytes: varlen
6003 return stateObj.WriteByteArray(b, length, 0, canAccumulate);
6006 case TdsEnums.SQLBIGVARCHAR:
6008 string s = (string)value;
6010 length = s.Length;
6011 WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj);
6012 WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info
6013 stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId
6014 WriteShort(length, stateObj);
6015 return WriteEncodingChar(s, _defaultEncoding, stateObj, canAccumulate);
6018 case TdsEnums.SQLUNIQUEID:
6020 System.Guid guid = (System.Guid)value;
6021 byte[] b = guid.ToByteArray();
6023 length = b.Length;
6024 Debug.Assert(length == 16, "Invalid length for guid type in com+ object");
6025 WriteSqlVariantHeader(18, metatype.TDSType, metatype.PropBytes, stateObj);
6026 stateObj.WriteByteArray(b, length, 0);
6027 break;
6030 case TdsEnums.SQLNVARCHAR:
6032 string s = (string)value;
6034 length = s.Length * 2;
6035 WriteSqlVariantHeader(9 + length, metatype.TDSType, metatype.PropBytes, stateObj);
6036 WriteUnsignedInt(_defaultCollation.info, stateObj); // propbytes: collation.Info
6037 stateObj.WriteByte(_defaultCollation.sortId); // propbytes: collation.SortId
6038 WriteShort(length, stateObj); // propbyte: varlen
6040 // string takes cchar, not cbyte so convert
6041 length >>= 1;
6042 return WriteString(s, length, 0, stateObj, canAccumulate);
6045 case TdsEnums.SQLDATETIME:
6047 TdsDateTime dt = MetaType.FromDateTime((DateTime)value, 8);
6049 WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj);
6050 WriteInt(dt.days, stateObj);
6051 WriteInt(dt.time, stateObj);
6052 break;
6055 case TdsEnums.SQLMONEY:
6057 WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj);
6058 WriteCurrency((Decimal)value, 8, stateObj);
6059 break;
6062 case TdsEnums.SQLNUMERICN:
6064 WriteSqlVariantHeader(21, metatype.TDSType, metatype.PropBytes, stateObj);
6065 stateObj.WriteByte(metatype.Precision); //propbytes: precision
6066 stateObj.WriteByte((byte)((Decimal.GetBits((Decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale
6067 WriteDecimal((Decimal)value, stateObj);
6068 break;
6071 case TdsEnums.SQLTIME:
6072 WriteSqlVariantHeader(8, metatype.TDSType, metatype.PropBytes, stateObj);
6073 stateObj.WriteByte(metatype.Scale); //propbytes: scale
6074 WriteTime((TimeSpan)value, metatype.Scale, 5, stateObj);
6075 break;
6077 case TdsEnums.SQLDATETIMEOFFSET:
6078 WriteSqlVariantHeader(13, metatype.TDSType, metatype.PropBytes, stateObj);
6079 stateObj.WriteByte(metatype.Scale); //propbytes: scale
6080 WriteDateTimeOffset((DateTimeOffset)value, metatype.Scale, 10, stateObj);
6081 break;
6083 default:
6084 Debug.Assert(false, "unknown tds type for sqlvariant!");
6085 break;
6086 } // switch
6087 // return point for accumualated writes, note: non-accumulated writes returned from their case statements
6088 return null;
6091 internal void WriteSqlVariantHeader(int length, byte tdstype, byte propbytes, TdsParserStateObject stateObj) {
6092 WriteInt(length, stateObj);
6093 stateObj.WriteByte(tdstype);
6094 stateObj.WriteByte(propbytes);
6097 internal void WriteSqlVariantDateTime2(DateTime value, TdsParserStateObject stateObj)
6099 MSS.SmiMetaData dateTime2MetaData = MSS.SmiMetaData.DefaultDateTime2;
6100 // NOTE: 3 bytes added here to support additional header information for variant - internal type, scale prop, scale
6101 WriteSqlVariantHeader((int)(dateTime2MetaData.MaxLength + 3), TdsEnums.SQLDATETIME2, 1 /* one scale prop */, stateObj);
6102 stateObj.WriteByte(dateTime2MetaData.Scale); //scale property
6103 WriteDateTime2(value, dateTime2MetaData.Scale, (int)(dateTime2MetaData.MaxLength), stateObj);
6106 internal void WriteSqlVariantDate(DateTime value, TdsParserStateObject stateObj)
6108 MSS.SmiMetaData dateMetaData = MSS.SmiMetaData.DefaultDate;
6109 // NOTE: 2 bytes added here to support additional header information for variant - internal type, scale prop (ignoring scale here)
6110 WriteSqlVariantHeader((int)(dateMetaData.MaxLength + 2), TdsEnums.SQLDATE, 0 /* one scale prop */, stateObj);
6111 WriteDate(value, stateObj);
6114 private byte[] SerializeSqlMoney(SqlMoney value, int length, TdsParserStateObject stateObj) {
6115 return SerializeCurrency(value.Value, length, stateObj);
6118 private void WriteSqlMoney(SqlMoney value, int length, TdsParserStateObject stateObj) {
6120 int[] bits = Decimal.GetBits(value.Value);
6122 // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by)
6123 bool isNeg = (0 != (bits[3] & unchecked((int)0x80000000)));
6124 long l = ((long)(uint)bits[1]) << 0x20 | (uint)bits[0];
6126 if (isNeg)
6127 l = -l;
6129 if (length == 4) {
6130 Decimal decimalValue = value.Value;
6132 // validate the value can be represented as a small money
6133 if (decimalValue < TdsEnums.SQL_SMALL_MONEY_MIN || decimalValue > TdsEnums.SQL_SMALL_MONEY_MAX) {
6134 throw SQL.MoneyOverflow(decimalValue.ToString(CultureInfo.InvariantCulture));
6137 WriteInt((int)l, stateObj);
6139 else {
6140 WriteInt((int)(l >> 0x20), stateObj);
6141 WriteInt((int)l, stateObj);
6145 private byte[] SerializeCurrency(Decimal value, int length, TdsParserStateObject stateObj) {
6146 SqlMoney m = new SqlMoney(value);
6147 int[] bits = Decimal.GetBits(m.Value);
6149 // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by)
6150 bool isNeg = (0 != (bits[3] & unchecked((int)0x80000000)));
6151 long l = ((long)(uint)bits[1]) << 0x20 | (uint)bits[0];
6153 if (isNeg)
6154 l = -l;
6156 if (length == 4) {
6157 // validate the value can be represented as a small money
6158 if (value < TdsEnums.SQL_SMALL_MONEY_MIN || value > TdsEnums.SQL_SMALL_MONEY_MAX) {
6159 throw SQL.MoneyOverflow(value.ToString(CultureInfo.InvariantCulture));
6162 // We normalize to allow conversion across data types. SMALLMONEY is serialized into a MONEY.
6163 length = 8;
6166 Debug.Assert (8 == length, "invalid length in SerializeCurrency");
6167 if (null == stateObj._bLongBytes) {
6168 stateObj._bLongBytes = new byte[8];
6171 byte[] bytes = stateObj._bLongBytes;
6172 int current = 0;
6174 byte[] bytesPart = SerializeInt((int)(l >> 0x20), stateObj);
6175 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6176 current += 4;
6178 bytesPart = SerializeInt((int)l, stateObj);
6179 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6181 return bytes;
6184 private void WriteCurrency(Decimal value, int length, TdsParserStateObject stateObj) {
6185 SqlMoney m = new SqlMoney(value);
6186 int[] bits = Decimal.GetBits(m.Value);
6188 // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by)
6189 bool isNeg = (0 != (bits[3] & unchecked((int)0x80000000)));
6190 long l = ((long)(uint)bits[1]) << 0x20 | (uint)bits[0];
6192 if (isNeg)
6193 l = -l;
6195 if (length == 4) {
6196 // validate the value can be represented as a small money
6197 if (value < TdsEnums.SQL_SMALL_MONEY_MIN || value > TdsEnums.SQL_SMALL_MONEY_MAX) {
6198 throw SQL.MoneyOverflow(value.ToString(CultureInfo.InvariantCulture));
6201 WriteInt((int)l, stateObj);
6203 else {
6204 WriteInt((int)(l >> 0x20), stateObj);
6205 WriteInt((int)l, stateObj);
6209 private byte[] SerializeDate(DateTime value) {
6210 long days = value.Subtract(DateTime.MinValue).Days;
6211 return SerializePartialLong(days, 3);
6214 private void WriteDate(DateTime value, TdsParserStateObject stateObj) {
6215 long days = value.Subtract(DateTime.MinValue).Days;
6216 WritePartialLong(days, 3, stateObj);
6219 private byte[] SerializeTime(TimeSpan value, byte scale, int length) {
6220 if (0 > value.Ticks || value.Ticks >= TimeSpan.TicksPerDay) {
6221 throw SQL.TimeOverflow(value.ToString());
6224 long time = value.Ticks / TdsEnums.TICKS_FROM_SCALE[scale];
6226 // We normalize to maximum precision to allow conversion across different precisions.
6227 time = time * TdsEnums.TICKS_FROM_SCALE[scale];
6228 length = TdsEnums.MAX_TIME_LENGTH;
6230 return SerializePartialLong(time, length);
6233 private void WriteTime(TimeSpan value, byte scale, int length, TdsParserStateObject stateObj) {
6234 if (0 > value.Ticks || value.Ticks >= TimeSpan.TicksPerDay) {
6235 throw SQL.TimeOverflow(value.ToString());
6237 long time = value.Ticks / TdsEnums.TICKS_FROM_SCALE[scale];
6238 WritePartialLong(time, length, stateObj);
6241 private byte[] SerializeDateTime2(DateTime value, byte scale, int length) {
6242 long time = value.TimeOfDay.Ticks / TdsEnums.TICKS_FROM_SCALE[scale]; // DateTime.TimeOfDay always returns a valid TimeSpan for Time
6244 // We normalize to maximum precision to allow conversion across different precisions.
6245 time = time * TdsEnums.TICKS_FROM_SCALE[scale];
6246 length = TdsEnums.MAX_DATETIME2_LENGTH;
6248 byte[] bytes = new byte[length];
6249 byte[] bytesPart;
6250 int current = 0;
6252 bytesPart = SerializePartialLong(time, length - 3);
6253 Buffer.BlockCopy(bytesPart, 0, bytes, current, length - 3);
6254 current += length - 3;
6256 bytesPart = SerializeDate(value);
6257 Buffer.BlockCopy(bytesPart, 0, bytes, current, 3);
6259 return bytes;
6262 private void WriteDateTime2(DateTime value, byte scale, int length, TdsParserStateObject stateObj) {
6263 long time = value.TimeOfDay.Ticks / TdsEnums.TICKS_FROM_SCALE[scale]; // DateTime.TimeOfDay always returns a valid TimeSpan for Time
6264 WritePartialLong(time, length - 3, stateObj);
6265 WriteDate(value, stateObj);
6268 private byte[] SerializeDateTimeOffset(DateTimeOffset value, byte scale, int length) {
6269 byte[] bytesPart;
6270 int current = 0;
6272 bytesPart = SerializeDateTime2(value.UtcDateTime, scale, length - 2);
6274 // We need to allocate the array after we have received the length of the serialized value
6275 // since it might be higher due to normalization.
6276 length = bytesPart.Length + 2;
6277 byte[] bytes = new byte[length];
6279 Buffer.BlockCopy(bytesPart, 0, bytes, current, length - 2);
6280 current += length - 2;
6282 Int16 offset = (Int16)value.Offset.TotalMinutes;
6283 bytes[current++] = (byte)(offset & 0xff);
6284 bytes[current++] = (byte)((offset >> 8) & 0xff);
6286 return bytes;
6289 private void WriteDateTimeOffset(DateTimeOffset value, byte scale, int length, TdsParserStateObject stateObj) {
6290 WriteDateTime2(value.UtcDateTime, scale, length - 2, stateObj);
6291 Int16 offset = (Int16)value.Offset.TotalMinutes;
6292 stateObj.WriteByte((byte)(offset & 0xff));
6293 stateObj.WriteByte((byte)((offset >> 8) & 0xff));
6296 private bool TryReadSqlDecimal(SqlBuffer value, int length, byte precision, byte scale, TdsParserStateObject stateObj) {
6297 byte byteValue;
6298 if (!stateObj.TryReadByte(out byteValue)) {
6299 return false;
6301 bool fPositive = (1 == byteValue);
6303 length = checked((int)length-1);
6305 int[] bits;
6306 if (!TryReadDecimalBits(length, stateObj, out bits)) {
6307 return false;
6310 value.SetToDecimal(precision, scale, fPositive, bits);
6311 return true;
6314 // @devnote: length should be size of decimal without the sign
6315 // @devnote: sign should have already been read off the wire
6316 private bool TryReadDecimalBits(int length, TdsParserStateObject stateObj, out int[] bits) {
6317 bits = stateObj._decimalBits; // used alloc'd array if we have one already
6318 int i;
6320 if (null == bits)
6321 bits = new int[4];
6322 else {
6323 for (i = 0; i < bits.Length; i++)
6324 bits[i] = 0;
6327 Debug.Assert((length > 0) &&
6328 (length <= TdsEnums.MAX_NUMERIC_LEN - 1) &&
6329 (length % 4 == 0), "decimal should have 4, 8, 12, or 16 bytes of data");
6331 int decLength = length >> 2;
6333 for (i = 0; i < decLength; i++) {
6334 // up to 16 bytes of data following the sign byte
6335 if (!stateObj.TryReadInt32(out bits[i])) {
6336 return false;
6340 return true;
6343 static internal SqlDecimal AdjustSqlDecimalScale(SqlDecimal d, int newScale) {
6344 if (d.Scale != newScale) {
6345 return SqlDecimal.AdjustScale(d, newScale - d.Scale, false /* Don't round, truncate. MDAC 69229 */);
6348 return d;
6351 static internal decimal AdjustDecimalScale(decimal value, int newScale) {
6352 int oldScale = (Decimal.GetBits(value)[3] & 0x00ff0000) >> 0x10;
6354 if (newScale != oldScale) {
6355 SqlDecimal num = new SqlDecimal(value);
6357 num = SqlDecimal.AdjustScale(num, newScale - oldScale, false /* Don't round, truncate. MDAC 69229 */);
6358 return num.Value;
6361 return value;
6364 internal byte[] SerializeSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) {
6365 if (null == stateObj._bDecimalBytes) {
6366 stateObj._bDecimalBytes = new byte[17];
6369 byte[] bytes = stateObj._bDecimalBytes;
6370 int current = 0;
6372 // sign
6373 if (d.IsPositive)
6374 bytes[current++] = 1;
6375 else
6376 bytes[current++] = 0;
6378 byte[] bytesPart = SerializeUnsignedInt(d.m_data1, stateObj);
6379 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6380 current += 4;
6381 bytesPart = SerializeUnsignedInt(d.m_data2, stateObj);
6382 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6383 current += 4;
6384 bytesPart = SerializeUnsignedInt(d.m_data3, stateObj);
6385 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6386 current += 4;
6387 bytesPart = SerializeUnsignedInt(d.m_data4, stateObj);
6388 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6390 return bytes;
6393 internal void WriteSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) {
6394 // sign
6395 if (d.IsPositive)
6396 stateObj.WriteByte(1);
6397 else
6398 stateObj.WriteByte(0);
6400 WriteUnsignedInt(d.m_data1, stateObj);
6401 WriteUnsignedInt(d.m_data2, stateObj);
6402 WriteUnsignedInt(d.m_data3, stateObj);
6403 WriteUnsignedInt(d.m_data4, stateObj);
6406 private byte[] SerializeDecimal(decimal value, TdsParserStateObject stateObj) {
6407 int[] decimalBits = Decimal.GetBits(value);
6408 if (null == stateObj._bDecimalBytes) {
6409 stateObj._bDecimalBytes = new byte[17];
6412 byte[] bytes = stateObj._bDecimalBytes;
6413 int current = 0;
6416 Returns a binary representation of a Decimal. The return value is an integer
6417 array with four elements. Elements 0, 1, and 2 contain the low, middle, and
6418 high 32 bits of the 96-bit integer part of the Decimal. Element 3 contains
6419 the scale factor and sign of the Decimal: bits 0-15 (the lower word) are
6420 unused; bits 16-23 contain a value between 0 and 28, indicating the power of
6421 10 to divide the 96-bit integer part by to produce the Decimal value; bits 24-
6422 30 are unused; and finally bit 31 indicates the sign of the Decimal value, 0
6423 meaning positive and 1 meaning negative.
6425 SQLDECIMAL/SQLNUMERIC has a byte stream of:
6426 struct {
6427 BYTE sign; // 1 if positive, 0 if negative
6428 BYTE data[];
6431 For TDS 7.0 and above, there are always 17 bytes of data
6434 // write the sign (note that COM and SQL are opposite)
6435 if (0x80000000 == (decimalBits[3] & 0x80000000))
6436 bytes[current++] = 0;
6437 else
6438 bytes[current++] = 1;
6440 byte[] bytesPart = SerializeInt(decimalBits[0], stateObj);
6441 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6442 current += 4;
6443 bytesPart = SerializeInt(decimalBits[1], stateObj);
6444 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6445 current += 4;
6446 bytesPart = SerializeInt(decimalBits[2], stateObj);
6447 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6448 current += 4;
6449 bytesPart = SerializeInt(0, stateObj);
6450 Buffer.BlockCopy(bytesPart, 0, bytes, current, 4);
6452 return bytes;
6455 private void WriteDecimal(decimal value, TdsParserStateObject stateObj) {
6456 stateObj._decimalBits = Decimal.GetBits(value);
6457 Debug.Assert(null != stateObj._decimalBits, "decimalBits should be filled in at TdsExecuteRPC time");
6460 Returns a binary representation of a Decimal. The return value is an integer
6461 array with four elements. Elements 0, 1, and 2 contain the low, middle, and
6462 high 32 bits of the 96-bit integer part of the Decimal. Element 3 contains
6463 the scale factor and sign of the Decimal: bits 0-15 (the lower word) are
6464 unused; bits 16-23 contain a value between 0 and 28, indicating the power of
6465 10 to divide the 96-bit integer part by to produce the Decimal value; bits 24-
6466 30 are unused; and finally bit 31 indicates the sign of the Decimal value, 0
6467 meaning positive and 1 meaning negative.
6469 SQLDECIMAL/SQLNUMERIC has a byte stream of:
6470 struct {
6471 BYTE sign; // 1 if positive, 0 if negative
6472 BYTE data[];
6475 For TDS 7.0 and above, there are always 17 bytes of data
6478 // write the sign (note that COM and SQL are opposite)
6479 if (0x80000000 == (stateObj._decimalBits[3] & 0x80000000))
6480 stateObj.WriteByte(0);
6481 else
6482 stateObj.WriteByte(1);
6484 WriteInt(stateObj._decimalBits[0], stateObj);
6485 WriteInt(stateObj._decimalBits[1], stateObj);
6486 WriteInt(stateObj._decimalBits[2], stateObj);
6487 WriteInt(0, stateObj);
6490 private void WriteIdentifier(string s, TdsParserStateObject stateObj) {
6491 if (null != s) {
6492 stateObj.WriteByte(checked((byte)s.Length));
6493 WriteString(s, stateObj);
6495 else {
6496 stateObj.WriteByte((byte)0);
6500 private void WriteIdentifierWithShortLength(string s, TdsParserStateObject stateObj) {
6501 if (null != s) {
6502 WriteShort(checked((short)s.Length), stateObj);
6503 WriteString(s, stateObj);
6505 else {
6506 WriteShort(0, stateObj);
6510 private Task WriteString(string s, TdsParserStateObject stateObj, bool canAccumulate = true) {
6511 return WriteString(s, s.Length, 0, stateObj, canAccumulate);
6514 internal byte[] SerializeCharArray(char[] carr, int length, int offset) {
6515 int cBytes = ADP.CharSize * length;
6516 byte[] bytes = new byte[cBytes];
6518 CopyCharsToBytes(carr, offset, bytes, 0, length);
6519 return bytes;
6522 internal Task WriteCharArray(char[] carr, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) {
6523 int cBytes = ADP.CharSize * length;
6525 // Perf shortcut: If it fits, write directly to the outBuff
6526 if(cBytes < (stateObj._outBuff.Length - stateObj._outBytesUsed)) {
6527 CopyCharsToBytes(carr, offset, stateObj._outBuff, stateObj._outBytesUsed, length);
6528 stateObj._outBytesUsed += cBytes;
6529 return null;
6531 else {
6532 if (stateObj._bTmp == null || stateObj._bTmp.Length < cBytes) {
6533 stateObj._bTmp = new byte[cBytes];
6536 CopyCharsToBytes(carr, offset, stateObj._bTmp, 0, length);
6537 return stateObj.WriteByteArray(stateObj._bTmp, cBytes, 0, canAccumulate);
6541 internal byte[] SerializeString(string s, int length, int offset) {
6542 int cBytes = ADP.CharSize * length;
6543 byte[] bytes = new byte[cBytes];
6545 CopyStringToBytes(s, offset, bytes, 0, length);
6546 return bytes;
6549 internal Task WriteString(string s, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) {
6550 int cBytes = ADP.CharSize * length;
6552 // Perf shortcut: If it fits, write directly to the outBuff
6553 if(cBytes < (stateObj._outBuff.Length - stateObj._outBytesUsed)) {
6554 CopyStringToBytes(s, offset, stateObj._outBuff, stateObj._outBytesUsed, length);
6555 stateObj._outBytesUsed += cBytes;
6556 return null;
6558 else {
6559 if (stateObj._bTmp == null || stateObj._bTmp.Length < cBytes) {
6560 stateObj._bTmp = new byte[cBytes];
6563 CopyStringToBytes(s, offset, stateObj._bTmp, 0, length);
6564 return stateObj.WriteByteArray(stateObj._bTmp, cBytes, 0, canAccumulate);
6569 private unsafe static void CopyCharsToBytes(char[] source, int sourceOffset, byte[] dest, int destOffset, int charLength) {
6570 // DEVNOTE: BE EXTREMELY CAREFULL in this method. Since it pins the buffers and copies the memory
6571 // directly, it bypasses all of the CLR's usual array-bounds checking. For maintainability, the checks
6572 // here should NOT be removed just because some other code will check them ahead of time.
6573 if (charLength < 0) {
6574 throw ADP.InvalidDataLength(charLength);
6577 if (checked(sourceOffset + charLength) > source.Length || sourceOffset < 0) {
6578 throw ADP.IndexOutOfRange(sourceOffset);
6581 // charLength >= 0 & checked conversion implies byteLength >= 0
6582 int byteLength = checked(charLength * ADP.CharSize);
6584 if (checked(destOffset + byteLength) > dest.Length || destOffset < 0) {
6585 throw ADP.IndexOutOfRange(destOffset);
6588 fixed (char* sourcePtr = source) {
6589 char* srcPtr = sourcePtr; // Can't increment the target of a Fixed statement
6590 srcPtr += sourceOffset; // char* increments by sizeof(char)
6591 fixed (byte* destinationPtr = dest) {
6592 byte* destPtr = destinationPtr;
6593 destPtr += destOffset;
6594 NativeOledbWrapper.MemoryCopy((IntPtr)destPtr, (IntPtr)srcPtr, byteLength);
6599 private unsafe static void CopyStringToBytes(string source, int sourceOffset, byte[] dest, int destOffset, int charLength) {
6600 // DEVNOTE: BE EXTREMELY CAREFULL in this method. Since it pins the buffers and copies the memory
6601 // directly, it bypasses all of the CLR's usual array-bounds checking. For maintainability, the checks
6602 // here should NOT be removed just because some other code will check them ahead of time.
6603 if (charLength < 0) {
6604 throw ADP.InvalidDataLength(charLength);
6607 if (checked(sourceOffset + charLength) > source.Length || sourceOffset < 0) {
6608 throw ADP.IndexOutOfRange(sourceOffset);
6611 // charLength >= 0 & checked conversion implies byteLength >= 0
6612 int byteLength = checked(charLength * ADP.CharSize);
6614 if (checked(destOffset + byteLength) > dest.Length || destOffset < 0) {
6615 throw ADP.IndexOutOfRange(destOffset);
6618 fixed (char* sourcePtr = source) {
6619 char* srcPtr = sourcePtr; // Can't increment the target of a Fixed statement
6620 srcPtr += sourceOffset; // char* increments by sizeof(char)
6621 fixed (byte* destinationPtr = dest) {
6622 byte* destPtr = destinationPtr;
6623 destPtr += destOffset;
6624 NativeOledbWrapper.MemoryCopy((IntPtr)destPtr, (IntPtr)srcPtr, byteLength);
6629 private Task WriteEncodingChar(string s, Encoding encoding, TdsParserStateObject stateObj, bool canAccumulate = true) {
6630 return WriteEncodingChar(s, s.Length, 0, encoding, stateObj, canAccumulate);
6633 private byte[] SerializeEncodingChar(string s, int numChars, int offset, Encoding encoding) {
6634 char[] charData;
6635 byte[] byteData = null;
6637 // if hitting 7.0 server, encoding will be null in metadata for columns or return values since
6638 // 7.0 has no support for multiple code pages in data - single code page support only
6639 if (encoding == null)
6640 encoding = _defaultEncoding;
6642 charData = s.ToCharArray(offset, numChars);
6644 byteData = new byte[encoding.GetByteCount(charData, 0, charData.Length)];
6645 encoding.GetBytes(charData, 0, charData.Length, byteData, 0);
6647 return byteData;
6650 private Task WriteEncodingChar(string s, int numChars, int offset, Encoding encoding, TdsParserStateObject stateObj, bool canAccumulate = true) {
6651 char[] charData;
6652 byte[] byteData;
6654 // if hitting 7.0 server, encoding will be null in metadata for columns or return values since
6655 // 7.0 has no support for multiple code pages in data - single code page support only
6656 if (encoding == null)
6657 encoding = _defaultEncoding;
6659 charData = s.ToCharArray(offset, numChars);
6661 // Optimization: if the entire string fits in the current buffer, then copy it directly
6662 int bytesLeft = stateObj._outBuff.Length - stateObj._outBytesUsed;
6663 if ((numChars <= bytesLeft) && (encoding.GetMaxByteCount(charData.Length) <= bytesLeft)) {
6664 int bytesWritten = encoding.GetBytes(charData, 0, charData.Length, stateObj._outBuff, stateObj._outBytesUsed);
6665 stateObj._outBytesUsed += bytesWritten;
6666 return null;
6668 else {
6669 byteData = encoding.GetBytes(charData, 0, numChars);
6670 Debug.Assert(byteData != null, "no data from encoding");
6671 return stateObj.WriteByteArray(byteData, byteData.Length, 0, canAccumulate);
6675 internal int GetEncodingCharLength(string value, int numChars, int charOffset, Encoding encoding) {
6678 if (value == null || value == ADP.StrEmpty) {
6679 return 0;
6682 // if hitting 7.0 server, encoding will be null in metadata for columns or return values since
6683 // 7.0 has no support for multiple code pages in data - single code page support only
6684 if (encoding == null) {
6685 if (null == _defaultEncoding) {
6686 ThrowUnsupportedCollationEncountered(null);
6689 encoding = _defaultEncoding;
6692 char[] charData = value.ToCharArray(charOffset, numChars);
6694 return encoding.GetByteCount(charData, 0, numChars);
6698 // Returns the data stream length of the data identified by tds type or SqlMetaData returns
6699 // Returns either the total size or the size of the first chunk for partially length prefixed types.
6701 internal bool TryGetDataLength(SqlMetaDataPriv colmeta, TdsParserStateObject stateObj, out ulong length) {
6702 // Handle Yukon specific tokens
6703 if (_isYukon && colmeta.metaType.IsPlp) {
6704 Debug.Assert(colmeta.tdsType == TdsEnums.SQLXMLTYPE ||
6705 colmeta.tdsType == TdsEnums.SQLBIGVARCHAR ||
6706 colmeta.tdsType == TdsEnums.SQLBIGVARBINARY ||
6707 colmeta.tdsType == TdsEnums.SQLNVARCHAR ||
6708 // Large UDTs is WinFS-only
6709 colmeta.tdsType == TdsEnums.SQLUDT,
6710 "GetDataLength:Invalid streaming datatype");
6711 return stateObj.TryReadPlpLength(true, out length);
6713 else {
6714 int intLength;
6715 if (!TryGetTokenLength(colmeta.tdsType, stateObj, out intLength)) {
6716 length = 0;
6717 return false;
6719 length = (ulong)intLength;
6720 return true;
6725 // returns the token length of the token or tds type
6726 // Returns -1 for partially length prefixed (plp) types for metadata info.
6727 // DOES NOT handle plp data streams correctly!!!
6728 // Plp data streams length information should be obtained from GetDataLength
6730 internal bool TryGetTokenLength(byte token, TdsParserStateObject stateObj, out int tokenLength) {
6731 Debug.Assert(token != 0, "0 length token!");
6733 switch (token) { // rules about SQLLenMask no longer apply to new tokens (as of 7.4)
6734 case TdsEnums.SQLFEATUREEXTACK:
6735 tokenLength = -1;
6736 return true;
6737 case TdsEnums.SQLSESSIONSTATE:
6738 case TdsEnums.SQLFEDAUTHINFO:
6739 return stateObj.TryReadInt32(out tokenLength);
6742 if (_isYukon) { // Handle Yukon specific exceptions
6743 if (token == TdsEnums.SQLUDT) { // special case for UDTs
6744 tokenLength = -1; // Should we return -1 or not call GetTokenLength for UDTs?
6745 return true;
6747 else if (token == TdsEnums.SQLRETURNVALUE) {
6748 tokenLength = -1; // In Yukon, the RETURNVALUE token stream no longer has length
6749 return true;
6751 else if (token == TdsEnums.SQLXMLTYPE) {
6752 ushort value;
6753 if (!stateObj.TryReadUInt16(out value)) {
6754 tokenLength = 0;
6755 return false;
6757 tokenLength = (int)value;
6758 Debug.Assert(tokenLength == TdsEnums.SQL_USHORTVARMAXLEN, "Invalid token stream for xml datatype");
6759 return true;
6763 switch (token & TdsEnums.SQLLenMask) {
6764 case TdsEnums.SQLFixedLen:
6765 tokenLength = ((0x01 << ((token & 0x0c) >> 2))) & 0xff;
6766 return true;
6767 case TdsEnums.SQLZeroLen:
6768 tokenLength = 0;
6769 return true;
6770 case TdsEnums.SQLVarLen:
6771 case TdsEnums.SQLVarCnt:
6772 if (0 != (token & 0x80)) {
6773 ushort value;
6774 if (!stateObj.TryReadUInt16(out value)) {
6775 tokenLength = 0;
6776 return false;
6778 tokenLength = value;
6779 return true;
6781 else if (0 == (token & 0x0c)) {
6783 if (!stateObj.TryReadInt32(out tokenLength)) {
6784 return false;
6786 return true;
6788 else {
6789 byte value;
6790 if (!stateObj.TryReadByte(out value)) {
6791 tokenLength = 0;
6792 return false;
6794 tokenLength = value;
6795 return true;
6797 default:
6798 Debug.Assert(false, "Unknown token length!");
6799 tokenLength = 0;
6800 return true;
6804 private void ProcessAttention(TdsParserStateObject stateObj) {
6805 if (_state == TdsParserState.Closed || _state == TdsParserState.Broken){
6806 return;
6808 Debug.Assert(stateObj._attentionSent, "invalid attempt to ProcessAttention, attentionSent == false!");
6810 // Attention processing scenarios:
6811 // 1) EOM packet with header ST_AACK bit plus DONE with status DONE_ATTN
6812 // 2) Packet without ST_AACK header bit but has DONE with status DONE_ATTN
6813 // 3) Secondary timeout occurs while reading, break connection
6815 // Since errors can occur and we need to cancel prior to throwing those errors, we
6816 // cache away error state and then process TDS for the attention. We restore those
6817 // errors after processing.
6818 stateObj.StoreErrorAndWarningForAttention();
6820 try {
6821 // Call run loop to process looking for attention ack.
6822 Run(RunBehavior.Attention, null, null, null, stateObj);
6824 catch (Exception e) {
6826 if (!ADP.IsCatchableExceptionType(e)) {
6827 throw;
6830 // If an exception occurs - break the connection.
6831 // Attention error will not be thrown in this case by Run(), but other failures may.
6832 ADP.TraceExceptionWithoutRethrow(e);
6833 _state = TdsParserState.Broken;
6834 _connHandler.BreakConnection();
6836 throw;
6839 stateObj.RestoreErrorAndWarningAfterAttention();
6841 Debug.Assert(!stateObj._attentionSent, "Invalid attentionSent state at end of ProcessAttention");
6844 static private int StateValueLength(int dataLen) {
6845 return dataLen < 0xFF ? (dataLen + 1) : (dataLen + 5);
6848 internal int WriteSessionRecoveryFeatureRequest(SessionData reconnectData, bool write /* if false just calculates the length */) {
6849 int len = 1;
6850 if (write) {
6851 _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_SRECOVERY);
6853 if (reconnectData == null) {
6854 if (write) {
6855 WriteInt(0, _physicalStateObj);
6857 len += 4;
6859 else {
6860 Debug.Assert(reconnectData._unrecoverableStatesCount == 0, "Unrecoverable state count should be 0");
6861 int initialLength = 0; // sizeof(DWORD) - length itself
6862 initialLength += 1 + 2 * TdsParserStaticMethods.NullAwareStringLength(reconnectData._initialDatabase);
6863 initialLength += 1 + 2 * TdsParserStaticMethods.NullAwareStringLength(reconnectData._initialLanguage);
6864 initialLength += (reconnectData._initialCollation == null) ? 1 : 6;
6865 for (int i = 0; i < SessionData._maxNumberOfSessionStates; i++) {
6866 if (reconnectData._initialState[i] != null) {
6867 initialLength += 1 /* StateId*/ + StateValueLength(reconnectData._initialState[i].Length);
6870 int currentLength = 0; // sizeof(DWORD) - length itself
6871 currentLength += 1 + 2 * (reconnectData._initialDatabase == reconnectData._database ? 0 : TdsParserStaticMethods.NullAwareStringLength(reconnectData._database));
6872 currentLength += 1 + 2 * (reconnectData._initialLanguage == reconnectData._language ? 0 : TdsParserStaticMethods.NullAwareStringLength(reconnectData._language));
6873 currentLength += (reconnectData._collation != null && !SqlCollation.AreSame(reconnectData._collation, reconnectData._initialCollation)) ? 6 : 1;
6874 bool[] writeState = new bool[SessionData._maxNumberOfSessionStates];
6875 for (int i = 0; i < SessionData._maxNumberOfSessionStates; i++) {
6876 if (reconnectData._delta[i] != null) {
6877 Debug.Assert(reconnectData._delta[i]._recoverable, "State should be recoverable");
6878 writeState[i] = true;
6879 if (reconnectData._initialState[i] != null && reconnectData._initialState[i].Length == reconnectData._delta[i]._dataLength) {
6880 writeState[i] = false;
6881 for (int j = 0; j < reconnectData._delta[i]._dataLength; j++) {
6882 if (reconnectData._initialState[i][j] != reconnectData._delta[i]._data[j]) {
6883 writeState[i] = true;
6884 break;
6888 if (writeState[i]) {
6889 currentLength += 1 /* StateId*/ + StateValueLength(reconnectData._delta[i]._dataLength);
6893 if (write) {
6894 WriteInt(8 + initialLength + currentLength, _physicalStateObj); // length of data w/o total length (initil+current+2*sizeof(DWORD))
6895 WriteInt(initialLength, _physicalStateObj);
6896 WriteIdentifier(reconnectData._initialDatabase, _physicalStateObj);
6897 WriteCollation(reconnectData._initialCollation, _physicalStateObj);
6898 WriteIdentifier(reconnectData._initialLanguage, _physicalStateObj);
6899 for (int i = 0; i < SessionData._maxNumberOfSessionStates; i++) {
6900 if (reconnectData._initialState[i] != null) {
6901 _physicalStateObj.WriteByte((byte)i);
6902 if (reconnectData._initialState[i].Length < 0xFF) {
6903 _physicalStateObj.WriteByte((byte)reconnectData._initialState[i].Length);
6905 else {
6906 _physicalStateObj.WriteByte(0xFF);
6907 WriteInt(reconnectData._initialState[i].Length, _physicalStateObj);
6909 _physicalStateObj.WriteByteArray(reconnectData._initialState[i], reconnectData._initialState[i].Length, 0);
6912 WriteInt(currentLength, _physicalStateObj);
6913 WriteIdentifier(reconnectData._database != reconnectData._initialDatabase ? reconnectData._database : null, _physicalStateObj);
6914 WriteCollation(SqlCollation.AreSame(reconnectData._initialCollation, reconnectData._collation) ? null : reconnectData._collation, _physicalStateObj);
6915 WriteIdentifier(reconnectData._language != reconnectData._initialLanguage ? reconnectData._language : null, _physicalStateObj);
6916 for (int i = 0; i < SessionData._maxNumberOfSessionStates; i++) {
6917 if (writeState[i]) {
6918 _physicalStateObj.WriteByte((byte)i);
6919 if (reconnectData._delta[i]._dataLength < 0xFF) {
6920 _physicalStateObj.WriteByte((byte)reconnectData._delta[i]._dataLength);
6922 else {
6923 _physicalStateObj.WriteByte(0xFF);
6924 WriteInt(reconnectData._delta[i]._dataLength, _physicalStateObj);
6926 _physicalStateObj.WriteByteArray(reconnectData._delta[i]._data, reconnectData._delta[i]._dataLength, 0);
6930 len += initialLength + currentLength + 12 /* length fields (initial, current, total) */;
6932 return len;
6935 internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionData fedAuthFeatureData,
6936 bool write /* if false just calculates the length */) {
6937 Debug.Assert(fedAuthFeatureData.libraryType == TdsEnums.FedAuthLibrary.ADAL || fedAuthFeatureData.libraryType == TdsEnums.FedAuthLibrary.SecurityToken,
6938 "only fed auth library type ADAL and Security Token are supported in writing feature request");
6940 int dataLen = 0;
6941 int totalLen = 0;
6943 // set dataLen and totalLen
6944 switch (fedAuthFeatureData.libraryType) {
6945 case TdsEnums.FedAuthLibrary.ADAL:
6946 dataLen = 2; // length of feature data = 1 byte for library and echo + 1 byte for workflow
6947 break;
6948 case TdsEnums.FedAuthLibrary.SecurityToken:
6949 Debug.Assert(fedAuthFeatureData.accessToken != null, "AccessToken should not be null.");
6950 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
6951 break;
6952 default:
6953 Debug.Assert(false, "Unrecognized library type for fedauth feature extension request");
6954 break;
6957 totalLen = dataLen + 5; // length of feature id (1 byte), data length field (4 bytes), and feature data (dataLen)
6959 // write feature id
6960 if (write) {
6961 _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_FEDAUTH);
6963 // set options
6964 byte options = 0x00;
6966 // set upper 7 bits of options to indicate fed auth library type
6967 switch (fedAuthFeatureData.libraryType) {
6968 case TdsEnums.FedAuthLibrary.ADAL:
6969 Debug.Assert(_connHandler._federatedAuthenticationInfoRequested == true, "_federatedAuthenticationInfoRequested field should be true");
6970 options |= TdsEnums.FEDAUTHLIB_ADAL << 1;
6971 break;
6972 case TdsEnums.FedAuthLibrary.SecurityToken:
6973 Debug.Assert(_connHandler._federatedAuthenticationRequested == true, "_federatedAuthenticationRequested field should be true");
6974 options |= TdsEnums.FEDAUTHLIB_SECURITYTOKEN << 1;
6975 break;
6976 default:
6977 Debug.Assert(false, "Unrecognized FedAuthLibrary type for feature extension request");
6978 break;
6981 options |= (byte)(fedAuthFeatureData.fedAuthRequiredPreLoginResponse == true ? 0x01 : 0x00);
6983 // write dataLen and options
6984 WriteInt(dataLen, _physicalStateObj);
6985 _physicalStateObj.WriteByte(options);
6987 // write workflow for FedAuthLibrary.ADAL
6988 // write accessToken for FedAuthLibrary.SecurityToken
6989 switch (fedAuthFeatureData.libraryType) {
6990 case TdsEnums.FedAuthLibrary.ADAL:
6991 byte workflow = 0x00;
6992 switch (fedAuthFeatureData.authentication) {
6993 case SqlAuthenticationMethod.ActiveDirectoryPassword:
6994 workflow = TdsEnums.ADALWORKFLOW_ACTIVEDIRECTORYPASSWORD;
6995 break;
6996 case SqlAuthenticationMethod.ActiveDirectoryIntegrated:
6997 workflow = TdsEnums.ADALWORKFLOW_ACTIVEDIRECTORYINTEGRATED;
6998 break;
6999 default:
7000 Debug.Assert(false, "Unrecognized Authentication type for fedauth ADAL request");
7001 break;
7004 _physicalStateObj.WriteByte(workflow);
7005 break;
7006 case TdsEnums.FedAuthLibrary.SecurityToken:
7007 WriteInt(fedAuthFeatureData.accessToken.Length, _physicalStateObj);
7008 _physicalStateObj.WriteByteArray(fedAuthFeatureData.accessToken, fedAuthFeatureData.accessToken.Length, 0);
7009 break;
7010 default:
7011 Debug.Assert(false, "Unrecognized FedAuthLibrary type for feature extension request");
7012 break;
7015 return totalLen;
7018 internal int WriteTceFeatureRequest (bool write /* if false just calculates the length */) {
7019 int len = 6; // (1byte = featureID, 4bytes = featureData length, 1 bytes = Version
7021 if (write) {
7022 // Write Feature ID, legth of the version# field and TCE Version#
7023 _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_TCE);
7024 WriteInt (1, _physicalStateObj);
7025 _physicalStateObj.WriteByte(TdsEnums.MAX_SUPPORTED_TCE_VERSION);
7028 return len; // size of data written
7031 internal int WriteGlobalTransactionsFeatureRequest(bool write /* if false just calculates the length */) {
7032 int len = 5; // 1byte = featureID, 4bytes = featureData length
7034 if (write) {
7035 // Write Feature ID
7036 _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS);
7037 WriteInt(0, _physicalStateObj); // we don't send any data
7040 return len;
7043 internal void TdsLogin(SqlLogin rec,
7044 TdsEnums.FeatureExtension requestedFeatures,
7045 SessionData recoverySessionData,
7046 FederatedAuthenticationFeatureExtensionData? fedAuthFeatureExtensionData) {
7047 _physicalStateObj.SetTimeoutSeconds(rec.timeout);
7049 Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request");
7050 Debug.Assert(TdsEnums.MAXLEN_HOSTNAME>=rec.hostName.Length, "_workstationId.Length exceeds the max length for this value");
7052 Debug.Assert(!(rec.useSSPI && _connHandler._fedAuthRequired), "Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option.");
7053 Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth");
7054 Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request");
7055 Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData.");
7057 Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_USERNAME >= rec.userName.Length), "_userID.Length exceeds the max length for this value");
7058 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");
7060 Debug.Assert(rec.password == null || (rec.password != null && TdsEnums.MAXLEN_PASSWORD>=rec.password.Length), "_password.Length exceeds the max length for this value");
7061 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");
7063 Debug.Assert(rec.credential != null || rec.userName != null || rec.password != null, "cannot mix the new secure password system and the connection string based password");
7064 Debug.Assert(rec.newSecurePassword != null || rec.newPassword != null, "cannot have both new secure change password and string based change password");
7065 Debug.Assert(TdsEnums.MAXLEN_APPNAME>=rec.applicationName.Length, "_applicationName.Length exceeds the max length for this value");
7066 Debug.Assert(TdsEnums.MAXLEN_SERVERNAME>=rec.serverName.Length, "_dataSource.Length exceeds the max length for this value");
7067 Debug.Assert(TdsEnums.MAXLEN_LANGUAGE>=rec.language.Length, "_currentLanguage .Length exceeds the max length for this value");
7068 Debug.Assert(TdsEnums.MAXLEN_DATABASE>=rec.database.Length, "_initialCatalog.Length exceeds the max length for this value");
7069 Debug.Assert(TdsEnums.MAXLEN_ATTACHDBFILE>=rec.attachDBFilename.Length, "_attachDBFileName.Length exceeds the max length for this value");
7071 Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point.");
7072 _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
7073 _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth);
7075 // get the password up front to use in sspi logic below
7076 byte[] encryptedPassword = null;
7077 byte[] encryptedChangePassword = null;
7078 int encryptedPasswordLengthInBytes;
7079 int encryptedChangePasswordLengthInBytes;
7080 bool useFeatureExt = (requestedFeatures != TdsEnums.FeatureExtension.None);
7082 string userName;
7084 if (rec.credential != null) {
7085 userName = rec.credential.UserId;
7086 encryptedPasswordLengthInBytes = rec.credential.Password.Length * 2;
7088 else {
7089 userName = rec.userName;
7090 encryptedPassword = TdsParserStaticMethods.EncryptPassword(rec.password);
7091 encryptedPasswordLengthInBytes = encryptedPassword.Length; // password in clear text is already encrypted and its length is in byte
7094 if (rec.newSecurePassword != null) {
7095 encryptedChangePasswordLengthInBytes = rec.newSecurePassword.Length * 2;
7097 else {
7098 encryptedChangePassword = TdsParserStaticMethods.EncryptPassword(rec.newPassword);
7099 encryptedChangePasswordLengthInBytes = encryptedChangePassword.Length;
7103 // set the message type
7104 _physicalStateObj._outputMessageType = TdsEnums.MT_LOGIN7;
7106 // length in bytes
7107 int length = TdsEnums.YUKON_LOG_REC_FIXED_LEN;
7109 string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME;
7110 Debug.Assert(TdsEnums.MAXLEN_CLIENTINTERFACE >= clientInterfaceName.Length, "cchCltIntName can specify at most 128 unicode characters. See Tds spec");
7112 // add up variable-len portions (multiply by 2 for byte len of char strings)
7114 checked {
7115 length += (rec.hostName.Length + rec.applicationName.Length +
7116 rec.serverName.Length + clientInterfaceName.Length +
7117 rec.language.Length + rec.database.Length +
7118 rec.attachDBFilename.Length) * 2;
7119 if (useFeatureExt) {
7120 length += 4;
7124 // allocate memory for SSPI variables
7125 byte[] outSSPIBuff = null;
7126 UInt32 outSSPILength = 0;
7128 // only add lengths of password and username if not using SSPI or requesting federated authentication info
7129 if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) {
7130 checked {
7131 length += (userName.Length * 2) + encryptedPasswordLengthInBytes
7132 + encryptedChangePasswordLengthInBytes;
7135 else {
7136 if (rec.useSSPI) {
7137 // now allocate proper length of buffer, and set length
7138 outSSPIBuff = new byte[s_maxSSPILength];
7139 outSSPILength = s_maxSSPILength;
7141 // Call helper function for SSPI data and actual length.
7142 // Since we don't have SSPI data from the server, send null for the
7143 // byte[] buffer and 0 for the int length.
7144 Debug.Assert(SniContext.Snix_Login==_physicalStateObj.SniContext, String.Format((IFormatProvider)null, "Unexpected SniContext. Expecting Snix_Login, actual value is '{0}'", _physicalStateObj.SniContext));
7145 _physicalStateObj.SniContext = SniContext.Snix_LoginSspi;
7146 SSPIData(null, 0, outSSPIBuff, ref outSSPILength);
7147 if (outSSPILength > Int32.MaxValue) {
7148 throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503
7150 _physicalStateObj.SniContext=SniContext.Snix_Login;
7152 checked {
7153 length += (Int32)outSSPILength;
7158 int feOffset = length;
7160 if (useFeatureExt) {
7161 checked {
7162 if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) {
7163 length += WriteSessionRecoveryFeatureRequest(recoverySessionData, false);
7165 if ((requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0) {
7166 Debug.Assert(fedAuthFeatureExtensionData != null, "fedAuthFeatureExtensionData should not null.");
7167 length += WriteFedAuthFeatureRequest(fedAuthFeatureExtensionData.Value, write:false);
7169 if ((requestedFeatures & TdsEnums.FeatureExtension.Tce) != 0) {
7170 length += WriteTceFeatureRequest (false);
7172 if ((requestedFeatures & TdsEnums.FeatureExtension.GlobalTransactions) != 0) {
7173 length += WriteGlobalTransactionsFeatureRequest(false);
7175 length++; // for terminator
7179 try {
7180 WriteInt(length, _physicalStateObj);
7181 if (recoverySessionData == null) {
7182 WriteInt((TdsEnums.DENALI_MAJOR << 24) | (TdsEnums.DENALI_INCREMENT << 16) | TdsEnums.DENALI_MINOR, _physicalStateObj);
7184 else {
7185 WriteUnsignedInt(recoverySessionData._tdsVersion, _physicalStateObj);
7187 WriteInt(rec.packetSize, _physicalStateObj);
7188 WriteInt(TdsEnums.CLIENT_PROG_VER, _physicalStateObj);
7189 WriteInt(TdsParserStaticMethods.GetCurrentProcessIdForTdsLoginOnly(), _physicalStateObj); //MDAC 84718
7190 WriteInt(0, _physicalStateObj); // connectionID is unused
7192 // Log7Flags (DWORD)
7193 int log7Flags = 0;
7196 Current snapshot from TDS spec with the offsets added:
7197 0) fByteOrder:1, // byte order of numeric data types on client
7198 1) fCharSet:1, // character set on client
7199 2) fFloat:2, // Type of floating point on client
7200 4) fDumpLoad:1, // Dump/Load and BCP enable
7201 5) fUseDb:1, // USE notification
7202 6) fDatabase:1, // Initial database fatal flag
7203 7) fSetLang:1, // SET LANGUAGE notification
7204 8) fLanguage:1, // Initial language fatal flag
7205 9) fODBC:1, // Set if client is ODBC driver
7206 10) fTranBoundary:1, // Transaction boundary notification
7207 11) fDelegatedSec:1, // Security with delegation is available
7208 12) fUserType:3, // Type of user
7209 15) fIntegratedSecurity:1, // Set if client is using integrated security
7210 16) fSQLType:4, // Type of SQL sent from client
7211 20) fOLEDB:1, // Set if client is OLEDB driver
7212 21) fSpare1:3, // first bit used for read-only intent, rest unused
7213 24) fResetPassword:1, // set if client wants to reset password
7214 25) fNoNBCAndSparse:1, // set if client does not support NBC and Sparse column
7215 26) fUserInstance:1, // This connection wants to connect to a SQL "user instance"
7216 27) fUnknownCollationHandling:1, // This connection can handle unknown collation correctly.
7217 28) fExtension:1 // Extensions are used
7218 32 - total
7221 // first byte
7222 log7Flags |= TdsEnums.USE_DB_ON << 5;
7223 log7Flags |= TdsEnums.INIT_DB_FATAL << 6;
7224 log7Flags |= TdsEnums.SET_LANG_ON << 7;
7226 // second byte
7227 log7Flags |= TdsEnums.INIT_LANG_FATAL << 8;
7228 log7Flags |= TdsEnums.ODBC_ON << 9;
7229 if (rec.useReplication) {
7230 log7Flags |= TdsEnums.REPL_ON << 12;
7232 if (rec.useSSPI) {
7233 log7Flags |= TdsEnums.SSPI_ON << 15;
7236 // third byte
7237 if (rec.readOnlyIntent) {
7238 log7Flags |= TdsEnums.READONLY_INTENT_ON << 21; // read-only intent flag is a first bit of fSpare1
7241 // 4th one
7242 if (!ADP.IsEmpty(rec.newPassword) || (rec.newSecurePassword != null && rec.newSecurePassword.Length != 0)) {
7243 log7Flags |= 1 << 24;
7245 if (rec.userInstance) {
7246 log7Flags |= 1 << 26;
7249 if (useFeatureExt) {
7250 log7Flags |= 1 << 28;
7253 WriteInt(log7Flags, _physicalStateObj);
7254 if (Bid.AdvancedOn) {
7255 Bid.Trace("<sc.TdsParser.TdsLogin|ADV> %d#, TDS Login7 flags = %d:\n", ObjectID, log7Flags);
7258 WriteInt(0, _physicalStateObj); // ClientTimeZone is not used
7259 WriteInt(0, _physicalStateObj); // LCID is unused by server
7261 // Start writing offset and length of variable length portions
7262 int offset = TdsEnums.YUKON_LOG_REC_FIXED_LEN;
7264 // write offset/length pairs
7266 // note that you must always set ibHostName since it indicaters the beginning of the variable length section of the login record
7267 WriteShort(offset, _physicalStateObj); // host name offset
7268 WriteShort(rec.hostName.Length, _physicalStateObj);
7269 offset += rec.hostName.Length * 2;
7271 // Only send user/password over if not fSSPI or fed auth ADAL... If both user/password and SSPI are in login
7272 // rec, only SSPI is used. Confirmed same bahavior as in luxor.
7273 if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) {
7274 WriteShort(offset, _physicalStateObj); // userName offset
7275 WriteShort(userName.Length, _physicalStateObj);
7276 offset += userName.Length * 2;
7278 // the encrypted password is a byte array - so length computations different than strings
7279 WriteShort(offset, _physicalStateObj); // password offset
7280 WriteShort(encryptedPasswordLengthInBytes / 2, _physicalStateObj);
7281 offset += encryptedPasswordLengthInBytes;
7283 else {
7284 // case where user/password data is not used, send over zeros
7285 WriteShort(0, _physicalStateObj); // userName offset
7286 WriteShort(0, _physicalStateObj);
7287 WriteShort(0, _physicalStateObj); // password offset
7288 WriteShort(0, _physicalStateObj);
7291 WriteShort(offset, _physicalStateObj); // app name offset
7292 WriteShort(rec.applicationName.Length, _physicalStateObj);
7293 offset += rec.applicationName.Length * 2;
7295 WriteShort(offset, _physicalStateObj); // server name offset
7296 WriteShort(rec.serverName.Length, _physicalStateObj);
7297 offset += rec.serverName.Length * 2;
7299 WriteShort(offset, _physicalStateObj);
7300 if (useFeatureExt) {
7301 WriteShort(4, _physicalStateObj); // length of ibFeatgureExtLong (which is a DWORD)
7302 offset += 4;
7304 else {
7305 WriteShort(0, _physicalStateObj); // unused (was remote password ?)
7308 WriteShort(offset, _physicalStateObj); // client interface name offset
7309 WriteShort(clientInterfaceName.Length, _physicalStateObj);
7310 offset += clientInterfaceName.Length * 2;
7312 WriteShort(offset, _physicalStateObj); // language name offset
7313 WriteShort(rec.language.Length, _physicalStateObj);
7314 offset += rec.language.Length * 2;
7316 WriteShort(offset, _physicalStateObj); // database name offset
7317 WriteShort(rec.database.Length, _physicalStateObj);
7318 offset += rec.database.Length * 2;
7322 if (null == s_nicAddress)
7323 s_nicAddress = TdsParserStaticMethods.GetNetworkPhysicalAddressForTdsLoginOnly();
7325 _physicalStateObj.WriteByteArray(s_nicAddress, s_nicAddress.Length, 0);
7327 WriteShort(offset, _physicalStateObj); // ibSSPI offset
7328 if (rec.useSSPI) {
7329 WriteShort((int)outSSPILength, _physicalStateObj);
7330 offset += (int)outSSPILength;
7332 else {
7333 WriteShort(0, _physicalStateObj);
7336 WriteShort(offset, _physicalStateObj); // DB filename offset
7337 WriteShort(rec.attachDBFilename.Length, _physicalStateObj);
7338 offset += rec.attachDBFilename.Length * 2;
7340 WriteShort(offset, _physicalStateObj); // reset password offset
7341 WriteShort(encryptedChangePasswordLengthInBytes / 2, _physicalStateObj);
7343 WriteInt(0, _physicalStateObj); // reserved for chSSPI
7345 // write variable length portion
7346 WriteString(rec.hostName, _physicalStateObj);
7348 // if we are using SSPI or fed auth ADAL, do not send over username/password, since we will use SSPI instead
7349 // same behavior as Luxor
7350 if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) {
7351 WriteString(userName, _physicalStateObj);
7353 // Cache offset in packet for tracing.
7354 _physicalStateObj._tracePasswordOffset = _physicalStateObj._outBytesUsed;
7355 _physicalStateObj._tracePasswordLength = encryptedPasswordLengthInBytes;
7357 if (rec.credential != null) {
7358 _physicalStateObj.WriteSecureString(rec.credential.Password);
7360 else {
7361 _physicalStateObj.WriteByteArray(encryptedPassword, encryptedPasswordLengthInBytes, 0);
7365 WriteString(rec.applicationName, _physicalStateObj);
7366 WriteString(rec.serverName, _physicalStateObj);
7368 // write ibFeatureExtLong
7369 if (useFeatureExt) {
7370 WriteInt(feOffset, _physicalStateObj);
7373 WriteString(clientInterfaceName, _physicalStateObj);
7374 WriteString(rec.language, _physicalStateObj);
7375 WriteString(rec.database, _physicalStateObj);
7377 // send over SSPI data if we are using SSPI
7378 if (rec.useSSPI)
7379 _physicalStateObj.WriteByteArray(outSSPIBuff, (int)outSSPILength, 0);
7381 WriteString(rec.attachDBFilename, _physicalStateObj);
7382 if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) {
7383 // Cache offset in packet for tracing.
7384 _physicalStateObj._traceChangePasswordOffset = _physicalStateObj._outBytesUsed;
7385 _physicalStateObj._traceChangePasswordLength = encryptedChangePasswordLengthInBytes;
7386 if (rec.newSecurePassword != null) {
7387 _physicalStateObj.WriteSecureString(rec.newSecurePassword);
7389 else {
7390 _physicalStateObj.WriteByteArray(encryptedChangePassword, encryptedChangePasswordLengthInBytes, 0);
7394 if (useFeatureExt) {
7395 if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) {
7396 WriteSessionRecoveryFeatureRequest(recoverySessionData, true);
7398 if ((requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0) {
7399 Bid.Trace("<sc.TdsParser.TdsLogin|SEC> Sending federated authentication feature request\n");
7400 Debug.Assert(fedAuthFeatureExtensionData != null, "fedAuthFeatureExtensionData should not null.");
7401 WriteFedAuthFeatureRequest(fedAuthFeatureExtensionData.Value, write: true);
7403 if ((requestedFeatures & TdsEnums.FeatureExtension.Tce) != 0) {
7404 WriteTceFeatureRequest (true);
7406 if ((requestedFeatures & TdsEnums.FeatureExtension.GlobalTransactions) != 0) {
7407 WriteGlobalTransactionsFeatureRequest(true);
7409 _physicalStateObj.WriteByte(0xFF); // terminator
7411 } // try
7412 catch (Exception e) {
7414 if (ADP.IsCatchableExceptionType(e)) {
7415 // be sure to wipe out our buffer if we started sending stuff
7416 _physicalStateObj._outputPacketNumber = 1; // end of message - reset to 1 - per ramas
7417 _physicalStateObj.ResetBuffer();
7420 throw;
7423 _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
7424 _physicalStateObj.ResetSecurePasswordsInfomation(); // Password information is needed only from Login process; done with writing login packet and should clear information
7425 _physicalStateObj._pendingData = true;
7426 _physicalStateObj._messageStatus = 0;
7427 }// tdsLogin
7429 /// <summary>
7430 /// Send the access token to the server.
7431 /// </summary>
7432 /// <param name="fedAuthToken">Type encapuslating a Federated Authentication access token.</param>
7433 internal void SendFedAuthToken(SqlFedAuthToken fedAuthToken) {
7434 Debug.Assert(fedAuthToken != null, "fedAuthToken cannot be null");
7435 Debug.Assert(fedAuthToken.accessToken != null, "fedAuthToken.accessToken cannot be null");
7438 Bid.Trace("<sc.TdsParser.SendFedAuthToken|SEC> Sending federated authentication token\n");
7440 _physicalStateObj._outputMessageType = TdsEnums.MT_FEDAUTH;
7442 byte[] accessToken = fedAuthToken.accessToken;
7444 // Send total length (length of token plus 4 bytes for the token length field)
7445 // If we were sending a nonce, this would include that length as well
7446 WriteUnsignedInt((uint)accessToken.Length + sizeof(uint), _physicalStateObj);
7448 // Send length of token
7449 WriteUnsignedInt((uint)accessToken.Length, _physicalStateObj);
7451 // Send federated authentication access token
7452 _physicalStateObj.WriteByteArray(accessToken, accessToken.Length, 0);
7454 _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
7455 _physicalStateObj._pendingData = true;
7456 _physicalStateObj._messageStatus = 0;
7458 _connHandler._federatedAuthenticationRequested = true;
7461 private void SSPIData(byte[] receivedBuff, UInt32 receivedLength, byte[] sendBuff, ref UInt32 sendLength) {
7462 SNISSPIData(receivedBuff, receivedLength, sendBuff, ref sendLength);
7465 private void SNISSPIData(byte[] receivedBuff, UInt32 receivedLength, byte[] sendBuff, ref UInt32 sendLength)
7467 if (receivedBuff == null)
7469 // we do not have SSPI data coming from server, so send over 0's for pointer and length
7470 receivedLength = 0;
7472 // we need to respond to the server's message with SSPI data
7473 if(0 != SNINativeMethodWrapper.SNISecGenClientContext(_physicalStateObj.Handle, receivedBuff, receivedLength, sendBuff, ref sendLength, _sniSpnBuffer))
7475 SSPIError(SQLMessage.SSPIGenerateError(), TdsEnums.GEN_CLIENT_CONTEXT);
7480 private void ProcessSSPI(int receivedLength) {
7481 SniContext outerContext=_physicalStateObj.SniContext;
7482 _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi;
7483 // allocate received buffer based on length from SSPI message
7484 byte[] receivedBuff = new byte[receivedLength];
7486 // read SSPI data received from server
7487 Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
7488 bool result = _physicalStateObj.TryReadByteArray(receivedBuff, 0, receivedLength);
7489 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
7491 // allocate send buffer and initialize length
7492 byte[] sendBuff = new byte[s_maxSSPILength];
7493 UInt32 sendLength = s_maxSSPILength;
7495 // make call for SSPI data
7496 SSPIData(receivedBuff, (UInt32)receivedLength, sendBuff, ref sendLength);
7498 // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA!
7499 _physicalStateObj.WriteByteArray(sendBuff, (int)sendLength, 0);
7501 // set message type so server knows its a SSPI response
7502 _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI;
7504 // send to server
7505 _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
7506 _physicalStateObj.SniContext=outerContext;
7509 private void SSPIError(string error, string procedure) {
7510 Debug.Assert(!ADP.IsEmpty(procedure), "TdsParser.SSPIError called with an empty or null procedure string");
7511 Debug.Assert(!ADP.IsEmpty(error), "TdsParser.SSPIError called with an empty or null error string");
7513 _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _server, error, procedure, 0));
7514 ThrowExceptionAndWarning(_physicalStateObj);
7517 private void LoadSSPILibrary() {
7518 // Outer check so we don't acquire lock once once it's loaded.
7519 if (!s_fSSPILoaded) {
7520 lock (s_tdsParserLock) {
7521 // re-check inside lock
7522 if (!s_fSSPILoaded) {
7523 // use local for ref param to defer setting s_maxSSPILength until we know the call succeeded.
7524 UInt32 maxLength = 0;
7525 if (0 != SNINativeMethodWrapper.SNISecInitPackage(ref maxLength))
7526 SSPIError(SQLMessage.SSPIInitializeError(), TdsEnums.INIT_SSPI_PACKAGE);
7528 s_maxSSPILength = maxLength;
7529 s_fSSPILoaded = true;
7535 if (s_maxSSPILength > Int32.MaxValue) {
7536 throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503
7540 private void LoadADALLibrary() {
7541 // Outer check so we don't acquire lock once once it's loaded.
7542 if (!s_fADALLoaded) {
7543 lock (s_tdsParserLock) {
7544 // re-check inside lock
7545 if (!s_fADALLoaded) {
7546 int result = ADALNativeWrapper.ADALInitialize();
7548 if (0 == result) {
7549 s_fADALLoaded = true;
7551 else {
7552 s_fADALLoaded = false;
7554 SqlAuthenticationMethod authentication = SqlAuthenticationMethod.NotSpecified;
7556 if (_connHandler.ConnectionOptions != null)
7558 authentication = _connHandler.ConnectionOptions.Authentication;
7560 // Only the below connection string options should have ended up calling this function.
7561 Debug.Assert(authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword);
7564 _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));
7566 ThrowExceptionAndWarning(_physicalStateObj);
7573 internal byte[] GetDTCAddress(int timeout, TdsParserStateObject stateObj) {
7574 // If this fails, the server will return a server error - Sameet Agarwal confirmed.
7575 // Success: DTCAddress returned. Failure: SqlError returned.
7577 byte[] dtcAddr = null;
7579 using (SqlDataReader dtcReader = TdsExecuteTransactionManagerRequest(
7580 null,
7581 TdsEnums.TransactionManagerRequestType.GetDTCAddress,
7582 null,
7583 TdsEnums.TransactionManagerIsolationLevel.Unspecified,
7584 timeout, null, stateObj, true)) {
7586 Debug.Assert(SniContext.Snix_Read==stateObj.SniContext, String.Format((IFormatProvider)null, "The SniContext should be Snix_Read but it actually is {0}", stateObj.SniContext));
7587 if (null != dtcReader && dtcReader.Read()) {
7588 Debug.Assert(dtcReader.GetName(0) == "TM Address", "TdsParser: GetDTCAddress did not return 'TM Address'");
7590 // DTCAddress is of variable size, and does not have a maximum. So we call GetBytes
7591 // to get the length of the dtcAddress, then allocate a byte array of that length,
7592 // then call GetBytes again on that byte[] with the length
7593 long dtcLength = dtcReader.GetBytes(0, 0, null, 0, 0);
7596 if (dtcLength <= Int32.MaxValue) {
7597 int cb = (int)dtcLength;
7599 dtcAddr = new byte[cb];
7600 dtcReader.GetBytes(0, 0, dtcAddr, 0, cb);
7602 #if DEBUG
7603 else {
7604 Debug.Assert(false, "unexpected length (> Int32.MaxValue) returned from dtcReader.GetBytes");
7605 // if we hit this case we'll just return a null address so that the user
7606 // will get a transcaction enlistment error in the upper layers
7608 #endif
7611 return dtcAddr;
7614 // Propagate the dtc cookie to the server, enlisting the connection.
7615 internal void PropagateDistributedTransaction(byte[] buffer, int timeout, TdsParserStateObject stateObj) {
7616 // if this fails, the server will return a server error - Sameet Agarwal confirmed
7617 // Success: server will return done token. Failure: SqlError returned.
7619 TdsExecuteTransactionManagerRequest(buffer,
7620 TdsEnums.TransactionManagerRequestType.Propagate, null,
7621 TdsEnums.TransactionManagerIsolationLevel.Unspecified, timeout, null, stateObj, true);
7624 internal SqlDataReader TdsExecuteTransactionManagerRequest(
7625 byte[] buffer,
7626 TdsEnums.TransactionManagerRequestType request,
7627 string transactionName,
7628 TdsEnums.TransactionManagerIsolationLevel isoLevel,
7629 int timeout,
7630 SqlInternalTransaction transaction,
7631 TdsParserStateObject stateObj,
7632 bool isDelegateControlRequest) {
7634 Debug.Assert(this == stateObj.Parser, "different parsers");
7636 if (TdsParserState.Broken == State || TdsParserState.Closed == State) {
7637 return null;
7640 // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
7641 // delegated transactions often happen while there is an open result
7642 // set, so we need to handle them by using a different MARS session,
7643 // otherwise we'll write on the physical state objects while someone
7644 // else is using it. When we don't have MARS enabled, we need to
7645 // lock the physical state object to syncronize it's use at least
7646 // until we increment the open results count. Once it's been
7647 // incremented the delegated transaction requests will fail, so they
7648 // won't stomp on anything.
7651 Debug.Assert(!_connHandler.ThreadHasParserLockForClose || _connHandler._parserLock.ThreadMayHaveLock(), "Thread claims to have parser lock, but lock is not taken");
7652 bool callerHasConnectionLock = _connHandler.ThreadHasParserLockForClose; // If the thread already claims to have the parser lock, then we will let the caller handle releasing it
7653 if (!callerHasConnectionLock) {
7654 _connHandler._parserLock.Wait(canReleaseFromAnyThread:false);
7655 _connHandler.ThreadHasParserLockForClose = true;
7657 // Capture _asyncWrite (after taking lock) to restore it afterwards
7658 bool hadAsyncWrites = _asyncWrite;
7659 try {
7660 // Temprarily disable async writes
7661 _asyncWrite = false;
7663 // This validation step MUST be done after locking the connection to guarantee we don't
7664 // accidentally execute after the transaction has completed on a different thread.
7665 if (!isDelegateControlRequest) {
7666 _connHandler.CheckEnlistedTransactionBinding();
7669 stateObj._outputMessageType = TdsEnums.MT_TRANS; // set message type
7670 stateObj.SetTimeoutSeconds(timeout);
7672 stateObj.SniContext = SniContext.Snix_Execute;
7674 if (_isYukon) {
7675 const int marsHeaderSize = 18; // 4 + 2 + 8 + 4
7676 const int totalHeaderLength = 22; // 4 + 4 + 2 + 8 + 4
7677 Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length");
7678 // Write total header length
7679 WriteInt(totalHeaderLength, stateObj);
7680 // Write mars header length
7681 WriteInt(marsHeaderSize, stateObj);
7682 WriteMarsHeaderData(stateObj, _currentTransaction);
7685 WriteShort((short)request, stateObj); // write TransactionManager Request type
7687 bool returnReader = false;
7689 switch (request) {
7690 case TdsEnums.TransactionManagerRequestType.GetDTCAddress:
7691 WriteShort(0, stateObj);
7693 returnReader = true;
7694 break;
7695 case TdsEnums.TransactionManagerRequestType.Propagate:
7696 if (null != buffer) {
7697 WriteShort(buffer.Length, stateObj);
7698 stateObj.WriteByteArray(buffer, buffer.Length, 0);
7700 else {
7701 WriteShort(0, stateObj);
7703 break;
7704 case TdsEnums.TransactionManagerRequestType.Begin:
7705 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for BeginTransaction!");
7706 Debug.Assert(null != transaction, "Should have specified an internalTransaction when doing a BeginTransaction request!");
7708 // Only assign the passed in transaction if it is not equal to the current transaction.
7709 // And, if it is not equal, the current actually should be null. Anything else
7710 // is a unexpected state. The concern here is mainly for the mixed use of
7711 // T-SQL and API transactions. See SQL BU DT 345300 for full details and repro.
7713 // Expected states:
7714 // 1) _pendingTransaction = null, _currentTransaction = null, non null transaction
7715 // passed in on BeginTransaction API call.
7716 // 2) _currentTransaction != null, _pendingTransaction = null, non null transaction
7717 // passed in but equivalent to _currentTransaction.
7719 // #1 will occur on standard BeginTransactionAPI call. #2 should only occur if
7720 // t-sql transaction started followed by a call to SqlConnection.BeginTransaction.
7721 // Any other state is unknown.
7722 if (_currentTransaction != transaction) {
7723 Debug.Assert(_currentTransaction == null || true == _fResetConnection, "We should not have a current Tx at this point");
7724 PendingTransaction = transaction;
7727 stateObj.WriteByte((byte)isoLevel);
7729 stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string).
7730 WriteString(transactionName, stateObj);
7731 break;
7732 case TdsEnums.TransactionManagerRequestType.Promote:
7733 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for PromoteTransaction!");
7734 // No payload - except current transaction in header
7735 // Promote returns a DTC cookie. However, the transaction cookie we use for the
7736 // connection does not change after a promote.
7737 break;
7738 case TdsEnums.TransactionManagerRequestType.Commit:
7739 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for CommitTransaction!");
7741 Debug.Assert(transactionName.Length == 0, "Should not have a transaction name on Commit");
7742 stateObj.WriteByte((byte)0); // No xact name
7744 stateObj.WriteByte(0); // No flags
7746 Debug.Assert(isoLevel == TdsEnums.TransactionManagerIsolationLevel.Unspecified, "Should not have isolation level other than unspecified on Commit!");
7747 // WriteByte((byte) 0, stateObj); // IsolationLevel
7748 // WriteByte((byte) 0, stateObj); // No begin xact name
7749 break;
7750 case TdsEnums.TransactionManagerRequestType.Rollback:
7751 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for RollbackTransaction!");
7753 stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string).
7754 WriteString(transactionName, stateObj);
7756 stateObj.WriteByte(0); // No flags
7758 Debug.Assert(isoLevel == TdsEnums.TransactionManagerIsolationLevel.Unspecified, "Should not have isolation level other than unspecified on Commit!");
7759 // WriteByte((byte) 0, stateObj); // IsolationLevel
7760 // WriteByte((byte) 0, stateObj); // No begin xact name
7761 break;
7762 case TdsEnums.TransactionManagerRequestType.Save:
7763 Debug.Assert(IsYukonOrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-Yukon clients for SaveTransaction!");
7765 stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string).
7766 WriteString(transactionName, stateObj);
7767 break;
7768 default:
7769 Debug.Assert(false, "Unexpected TransactionManagerRequest");
7770 break;
7773 Task writeTask = stateObj.WritePacket(TdsEnums.HARDFLUSH);
7774 Debug.Assert(writeTask == null, "Writes should not pend when writing sync");
7775 stateObj._pendingData = true;
7776 stateObj._messageStatus = 0;
7778 SqlDataReader dtcReader = null;
7779 stateObj.SniContext = SniContext.Snix_Read;
7780 if (returnReader) {
7781 dtcReader = new SqlDataReader(null, CommandBehavior.Default);
7782 Debug.Assert(this == stateObj.Parser, "different parser");
7783 #if DEBUG
7784 // Remove the current owner of stateObj - otherwise we will hit asserts
7785 stateObj.Owner = null;
7786 #endif
7787 dtcReader.Bind(stateObj);
7789 // force consumption of metadata
7790 _SqlMetaDataSet metaData = dtcReader.MetaData;
7792 else {
7793 Run(RunBehavior.UntilDone, null, null, null, stateObj);
7796 // If the retained ID is no longer valid (because we are enlisting in null or a new transaction) then it should be cleared
7797 if (((request == TdsEnums.TransactionManagerRequestType.Begin) || (request == TdsEnums.TransactionManagerRequestType.Propagate)) && ((transaction == null) || (transaction.TransactionId != _retainedTransactionId))) {
7798 _retainedTransactionId = SqlInternalTransaction.NullTransactionId;
7801 return dtcReader;
7803 catch (Exception e) {
7805 if (!ADP.IsCatchableExceptionType(e)) {
7806 throw;
7809 FailureCleanup(stateObj, e);
7811 throw;
7813 finally {
7814 // SQLHotfix 50000518
7815 // make sure we don't leave temporary fields set when leaving this function
7816 _pendingTransaction = null;
7818 _asyncWrite = hadAsyncWrites;
7820 if (!callerHasConnectionLock) {
7821 _connHandler.ThreadHasParserLockForClose = false;
7822 _connHandler._parserLock.Release();
7827 internal void FailureCleanup(TdsParserStateObject stateObj, Exception e) {
7828 int old_outputPacketNumber = stateObj._outputPacketNumber;
7830 if (Bid.TraceOn) {
7831 Bid.Trace("<sc.TdsParser.FailureCleanup|ERR> Exception caught on ExecuteXXX: '%ls' \n", e.ToString());
7834 if (stateObj.HasOpenResult) { // SQL BU DT 383773 - need to decrement openResultCount if operation failed.
7835 stateObj.DecrementOpenResultCount();
7838 // be sure to wipe out our buffer if we started sending stuff
7839 stateObj.ResetBuffer();
7840 stateObj._outputPacketNumber = 1; // end of message - reset to 1 - per ramas
7842 if (old_outputPacketNumber != 1 && _state == TdsParserState.OpenLoggedIn) {
7843 Debug.Assert(_connHandler._parserLock.ThreadMayHaveLock(), "Should not be calling into FailureCleanup without first taking the parser lock");
7845 bool originalThreadHasParserLock = _connHandler.ThreadHasParserLockForClose;
7846 try {
7847 // Dev11 Bug 385286 : ExecuteNonQueryAsync hangs when trying to write a parameter which generates ArgumentException and while handling that exception the server disconnects the connection
7848 // Need to set this to true such that if we have an error sending\processing the attention, we won't deadlock ourselves
7849 _connHandler.ThreadHasParserLockForClose = true;
7851 // If _outputPacketNumber prior to ResetBuffer was not equal to 1, a packet was already
7852 // sent to the server and so we need to send an attention and process the attention ack.
7853 stateObj.SendAttention();
7854 ProcessAttention(stateObj);
7856 finally {
7857 // Reset the ThreadHasParserLock value incase our caller expects it to be set\not set
7858 _connHandler.ThreadHasParserLockForClose = originalThreadHasParserLock;
7862 Bid.Trace("<sc.TdsParser.FailureCleanup|ERR> Exception rethrown. \n");
7865 internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool sync, bool callerHasConnectionLock = false) {
7866 if (TdsParserState.Broken == State || TdsParserState.Closed == State) {
7867 return null;
7870 if (stateObj.BcpLock) {
7871 throw SQL.ConnectionLockedForBcpEvent();
7874 // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
7875 // delegated transactions often happen while there is an open result
7876 // set, so we need to handle them by using a different MARS session,
7877 // otherwise we'll write on the physical state objects while someone
7878 // else is using it. When we don't have MARS enabled, we need to
7879 // lock the physical state object to syncronize it's use at least
7880 // until we increment the open results count. Once it's been
7881 // incremented the delegated transaction requests will fail, so they
7882 // won't stomp on anything.
7884 // Only need to take the lock if neither the thread nor the caller claims to already have it
7885 bool needToTakeParserLock = (!callerHasConnectionLock) && (!_connHandler.ThreadHasParserLockForClose);
7886 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
7887 Debug.Assert(needToTakeParserLock || _connHandler._parserLock.ThreadMayHaveLock(), "Thread or caller claims to have connection lock, but lock is not taken");
7889 bool releaseConnectionLock = false;
7890 if (needToTakeParserLock) {
7891 _connHandler._parserLock.Wait(canReleaseFromAnyThread: !sync);
7892 releaseConnectionLock = true;
7895 // Switch the writing mode
7896 // NOTE: We are not turning off async writes when we complete since SqlBulkCopy uses this method and expects _asyncWrite to not change
7897 _asyncWrite = !sync;
7899 try {
7900 // Check that the connection is still alive
7901 if ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken)) {
7902 throw ADP.ClosedConnectionError();
7905 // This validation step MUST be done after locking the connection to guarantee we don't
7906 // accidentally execute after the transaction has completed on a different thread.
7907 _connHandler.CheckEnlistedTransactionBinding();
7909 stateObj.SetTimeoutSeconds(timeout);
7910 if ((!_fMARS) && (_physicalStateObj.HasOpenResult))
7912 Bid.Trace("<sc.TdsParser.TdsExecuteSQLBatch|ERR> Potential multi-threaded misuse of connection, non-MARs connection with an open result %d#\n", ObjectID);
7914 stateObj.SniContext = SniContext.Snix_Execute;
7916 if (_isYukon) {
7918 WriteRPCBatchHeaders(stateObj, notificationRequest);
7921 stateObj._outputMessageType = TdsEnums.MT_SQL;
7923 WriteString(text, text.Length, 0, stateObj);
7925 Task executeTask = stateObj.ExecuteFlush();
7926 if (executeTask == null) {
7927 stateObj.SniContext = SniContext.Snix_Read;
7929 else {
7930 Debug.Assert(!sync, "Should not have gotten a Task when writing in sync mode");
7932 // Need to wait for flush - continuation will unlock the connection
7933 bool taskReleaseConnectionLock = releaseConnectionLock;
7934 releaseConnectionLock = false;
7935 return executeTask.ContinueWith(t => {
7936 Debug.Assert(!t.IsCanceled, "Task should not be canceled");
7937 try {
7938 if (t.IsFaulted) {
7939 FailureCleanup(stateObj, t.Exception.InnerException);
7940 throw t.Exception.InnerException;
7942 else {
7943 stateObj.SniContext = SniContext.Snix_Read;
7946 finally {
7947 if (taskReleaseConnectionLock) {
7948 _connHandler._parserLock.Release();
7951 }, TaskScheduler.Default);
7954 // Finished sync
7955 return null;
7957 catch (Exception e) {
7958 //Debug.Assert(_state == TdsParserState.Broken, "Caught exception in TdsExecuteSQLBatch but connection was not broken!");
7960 if (!ADP.IsCatchableExceptionType(e)) {
7961 throw;
7964 FailureCleanup(stateObj, e);
7966 throw;
7968 finally {
7969 if (releaseConnectionLock) {
7970 _connHandler._parserLock.Release();
7975 internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync = true,
7976 TaskCompletionSource<object> completion = null, int startRpc = 0, int startParam = 0) {
7977 bool firstCall = (completion == null);
7978 bool releaseConnectionLock = false;
7980 Debug.Assert(cmd != null, @"cmd cannot be null inside TdsExecuteRPC");
7981 Debug.Assert(!firstCall || startRpc == 0, "startRpc is not 0 on first call");
7982 Debug.Assert(!firstCall || startParam == 0, "startParam is not 0 on first call");
7983 Debug.Assert(!firstCall || !_connHandler.ThreadHasParserLockForClose, "Thread should not already have connection lock");
7984 Debug.Assert(firstCall || _connHandler._parserLock.ThreadMayHaveLock(), "Connection lock not taken after the first call");
7985 try {
7986 _SqlRPC rpcext = null;
7987 int tempLen;
7989 // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
7990 // delegated transactions often happen while there is an open result
7991 // set, so we need to handle them by using a different MARS session,
7992 // otherwise we'll write on the physical state objects while someone
7993 // else is using it. When we don't have MARS enabled, we need to
7994 // lock the physical state object to syncronize it's use at least
7995 // until we increment the open results count. Once it's been
7996 // incremented the delegated transaction requests will fail, so they
7997 // won't stomp on anything.
8000 if (firstCall) {
8001 _connHandler._parserLock.Wait(canReleaseFromAnyThread:!sync);
8002 releaseConnectionLock = true;
8004 try {
8005 // Ensure that connection is alive
8006 if ((TdsParserState.Broken == State) || (TdsParserState.Closed == State)) {
8007 throw ADP.ClosedConnectionError();
8010 // This validation step MUST be done after locking the connection to guarantee we don't
8011 // accidentally execute after the transaction has completed on a different thread.
8012 if (firstCall) {
8013 _asyncWrite = !sync;
8015 _connHandler.CheckEnlistedTransactionBinding();
8017 stateObj.SetTimeoutSeconds(timeout);
8018 if ((!_fMARS) && (_physicalStateObj.HasOpenResult))
8020 Bid.Trace("<sc.TdsParser.TdsExecuteRPC|ERR> Potential multi-threaded misuse of connection, non-MARs connection with an open result %d#\n", ObjectID);
8022 stateObj.SniContext = SniContext.Snix_Execute;
8024 if (_isYukon) {
8026 WriteRPCBatchHeaders(stateObj, notificationRequest);
8029 stateObj._outputMessageType = TdsEnums.MT_RPC;
8032 for (int ii = startRpc; ii < rpcArray.Length; ii++) {
8033 rpcext = rpcArray[ii];
8035 if (startParam == 0 || ii > startRpc) {
8036 if (rpcext.ProcID != 0 && _isShiloh) {
8037 // Perf optimization for Shiloh and later,
8038 Debug.Assert(rpcext.ProcID < 255, "rpcExec:ProcID can't be larger than 255");
8039 WriteShort(0xffff, stateObj);
8040 WriteShort((short)(rpcext.ProcID), stateObj);
8042 else {
8043 Debug.Assert(!ADP.IsEmpty(rpcext.rpcName), "must have an RPC name");
8044 tempLen = rpcext.rpcName.Length;
8045 WriteShort(tempLen, stateObj);
8046 WriteString(rpcext.rpcName, tempLen, 0, stateObj);
8049 // Options
8050 WriteShort((short)rpcext.options, stateObj);
8053 // Stream out parameters
8054 SqlParameter[] parameters = rpcext.parameters;
8056 for (int i = (ii == startRpc) ? startParam : 0; i < parameters.Length; i++) {
8057 // Debug.WriteLine("i: " + i.ToString(CultureInfo.InvariantCulture));
8058 // parameters can be unnamed
8059 SqlParameter param = parameters[i];
8060 // Since we are reusing the parameters array, we cannot rely on length to indicate no of parameters.
8061 if (param == null)
8062 break; // End of parameters for this execute
8064 // Throw an exception if ForceColumnEncryption is set on a parameter and the ColumnEncryption is not enabled on SqlConnection or SqlCommand
8065 if (param.ForceColumnEncryption &&
8066 !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled ||
8067 (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled))) {
8068 throw SQL.ParamInvalidForceColumnEncryptionSetting(param.ParameterName, rpcext.GetCommandTextOrRpcName());
8071 // Check if the applications wants to force column encryption to avoid sending sensitive data to server
8072 if (param.ForceColumnEncryption && param.CipherMetadata == null
8073 && (param.Direction == ParameterDirection.Input || param.Direction == ParameterDirection.InputOutput)) {
8074 // Application wants a parameter to be encrypted before sending it to server, however server doesnt think this parameter needs encryption.
8075 throw SQL.ParamUnExpectedEncryptionMetadata(param.ParameterName, rpcext.GetCommandTextOrRpcName());
8078 // Validate parameters are not variable length without size and with null value. MDAC 66522
8079 param.Validate(i, isCommandProc);
8081 // type (parameter record stores the MetaType class which is a helper that encapsulates all the type information we need here)
8082 MetaType mt = param.InternalMetaType;
8084 if (mt.IsNewKatmaiType) {
8085 WriteSmiParameter(param, i, 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_DEFAULT), stateObj);
8086 continue;
8089 if ((!_isShiloh && !mt.Is70Supported) ||
8090 (!_isYukon && !mt.Is80Supported) ||
8091 (!_isKatmai && !mt.Is90Supported)) {
8092 throw ADP.VersionDoesNotSupportDataType(mt.TypeName);
8094 object value = null;
8095 bool isNull = true;
8096 bool isSqlVal = false;
8097 bool isDataFeed = false;
8098 // if we have an output param, set the value to null so we do not send it across to the server
8099 if (param.Direction == ParameterDirection.Output) {
8100 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.
8101 param.Value = null;
8102 param.ParamaterIsSqlType = isSqlVal;
8104 else {
8105 value = param.GetCoercedValue();
8106 isNull = param.IsNull;
8107 if (!isNull) {
8108 isSqlVal = param.CoercedValueIsSqlType;
8109 isDataFeed = param.CoercedValueIsDataFeed;
8113 WriteParameterName(param.ParameterNameFixed, stateObj);
8115 // Write parameter status
8116 stateObj.WriteByte(rpcext.paramoptions[i]);
8118 // MaxLen field is only written out for non-fixed length data types
8119 // use the greater of the two sizes for maxLen
8120 int actualSize;
8121 int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize();
8123 //for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation
8124 if (mt.TDSType != TdsEnums.SQLUDT)
8125 // getting the actualSize is expensive, cache here and use below
8126 actualSize = param.GetActualSize();
8127 else
8128 actualSize = 0; //get this later
8130 byte precision = 0;
8131 byte scale = 0;
8133 // scale and precision are only relevant for numeric and decimal types
8134 // adjust the actual value scale and precision to match the user specified
8135 if (mt.SqlDbType == SqlDbType.Decimal) {
8136 precision = param.GetActualPrecision();
8137 scale = param.GetActualScale();
8139 if (precision > TdsEnums.MAX_NUMERIC_PRECISION) {
8140 throw SQL.PrecisionValueOutOfRange(precision);
8143 // bug 49512, make sure the value matches the scale the user enters
8144 if (!isNull) {
8145 if (isSqlVal) {
8146 value = AdjustSqlDecimalScale((SqlDecimal)value, scale);
8148 // If Precision is specified, verify value precision vs param precision
8149 if (precision != 0) {
8150 if (precision < ((SqlDecimal)value).Precision) {
8151 throw ADP.ParameterValueOutOfRange((SqlDecimal)value);
8155 else {
8156 value = AdjustDecimalScale((Decimal)value, scale);
8158 SqlDecimal sqlValue = new SqlDecimal((Decimal)value);
8160 // If Precision is specified, verify value precision vs param precision
8161 if (precision != 0) {
8162 if (precision < sqlValue.Precision) {
8163 throw ADP.ParameterValueOutOfRange((Decimal)value);
8170 bool isParameterEncrypted = 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_ENCRYPTED);
8172 // Additional information we need to send over wire to the server when writing encrypted parameters.
8173 SqlColumnEncryptionInputParameterInfo encryptedParameterInfoToWrite = null;
8175 // If the parameter is encrypted, we need to encrypt the value.
8176 if (isParameterEncrypted) {
8177 Debug.Assert(mt.TDSType != TdsEnums.SQLVARIANT &&
8178 mt.TDSType != TdsEnums.SQLUDT &&
8179 mt.TDSType != TdsEnums.SQLXMLTYPE &&
8180 mt.TDSType != TdsEnums.SQLIMAGE &&
8181 mt.TDSType != TdsEnums.SQLTEXT &&
8182 mt.TDSType != TdsEnums.SQLNTEXT, "Type unsupported for encryption");
8184 byte[] serializedValue = null;
8185 byte[] encryptedValue = null;
8187 if (!isNull) {
8188 try {
8189 if (isSqlVal) {
8190 serializedValue = SerializeUnencryptedSqlValue(value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj);
8192 else {
8193 // for codePageEncoded types, WriteValue simply expects the number of characters
8194 // For plp types, we also need the encoded byte size
8195 serializedValue = SerializeUnencryptedValue(value, mt, param.GetActualScale(), actualSize, param.Offset, isDataFeed, param.NormalizationRuleVersion, stateObj);
8198 Debug.Assert(serializedValue != null, "serializedValue should not be null in TdsExecuteRPC.");
8199 encryptedValue = SqlSecurityUtility.EncryptWithKey(serializedValue, param.CipherMetadata, _connHandler.ConnectionOptions.DataSource);
8201 catch (Exception e) {
8202 throw SQL.ParamEncryptionFailed(param.ParameterName, null, e);
8205 Debug.Assert(encryptedValue != null && encryptedValue.Length > 0,
8206 "encryptedValue should not be null or empty in TdsExecuteRPC.");
8208 else {
8209 encryptedValue = null;
8212 // Change the datatype to varbinary(max).
8213 // Since we don't know the size of the encrypted parameter on the server side, always set to (max).
8215 mt = MetaType.MetaMaxVarBinary;
8216 size = -1;
8217 actualSize = (encryptedValue == null) ? 0 : encryptedValue.Length;
8219 encryptedParameterInfoToWrite = new SqlColumnEncryptionInputParameterInfo(param.GetMetadataForTypeInfo(),
8220 param.CipherMetadata);
8222 // Set the value to the encrypted value and mark isSqlVal as false for VARBINARY encrypted value.
8223 value = encryptedValue;
8224 isSqlVal = false;
8227 Debug.Assert(isParameterEncrypted == (encryptedParameterInfoToWrite != null),
8228 "encryptedParameterInfoToWrite can be not null if and only if isParameterEncrypted is true.");
8230 Debug.Assert(!isSqlVal || !isParameterEncrypted, "isParameterEncrypted can be true only if isSqlVal is false.");
8233 // fixup the types by using the NullableType property of the MetaType class
8235 // following rules should be followed based on feedback from the M-SQL team
8236 // 1) always use the BIG* types (ex: instead of SQLCHAR use SQLBIGCHAR)
8237 // 2) always use nullable types (ex: instead of SQLINT use SQLINTN)
8238 // 3) DECIMALN should always be sent as NUMERICN
8240 stateObj.WriteByte(mt.NullableType);
8242 // handle variants here: the SQLVariant writing routine will write the maxlen and actual len columns
8243 if (mt.TDSType == TdsEnums.SQLVARIANT) {
8244 // devnote: Do we ever hit this codepath? Yes, when a null value is being writen out via a sql variant
8245 // param.GetActualSize is not used
8246 WriteSqlVariantValue(isSqlVal ? MetaType.GetComValueFromSqlVariant(value) : value, param.GetActualSize(), param.Offset, stateObj);
8247 continue;
8250 int codePageByteSize = 0;
8251 int maxsize = 0;
8253 if (mt.IsAnsiType) {
8254 // Avoid the following code block if ANSI but unfilled LazyMat blob
8255 if ((!isNull) && (!isDataFeed)) {
8256 string s;
8258 if (isSqlVal) {
8259 if (value is SqlString) {
8260 s = ((SqlString)value).Value;
8262 else {
8263 Debug.Assert(value is SqlChars, "Unknown value for Ansi datatype");
8264 s = new String(((SqlChars)value).Value);
8267 else {
8268 s = (string)value;
8271 codePageByteSize = GetEncodingCharLength(s, actualSize, param.Offset, _defaultEncoding);
8274 if (mt.IsPlp) {
8275 WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj);
8277 else {
8278 maxsize = (size > codePageByteSize) ? size : codePageByteSize;
8279 if (maxsize == 0) {
8280 // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322)
8281 if (mt.IsNCharType)
8282 maxsize = 2;
8283 else
8284 maxsize = 1;
8287 WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj);
8290 else {
8291 // If type timestamp - treat as fixed type and always send over timestamp length, which is 8.
8292 // For fixed types, we either send null or fixed length for type length. We want to match that
8293 // behavior for timestamps. However, in the case of null, we still must send 8 because if we
8294 // send null we will not receive a output val. You can send null for fixed types and still
8295 // receive a output value, but not for variable types. So, always send 8 for timestamp because
8296 // while the user sees it as a fixed type, we are actually representing it as a bigbinary which
8297 // is variable.
8298 if (mt.SqlDbType == SqlDbType.Timestamp) {
8299 WriteParameterVarLen(mt, TdsEnums.TEXT_TIME_STAMP_LEN, false, stateObj);
8301 else if (mt.SqlDbType == SqlDbType.Udt) {
8302 byte[] udtVal = null;
8303 Microsoft.SqlServer.Server.Format format = Microsoft.SqlServer.Server.Format.Native;
8305 Debug.Assert(_isYukon, "Invalid DataType UDT for non-Yukon or later server!");
8307 if (!isNull) {
8308 udtVal = _connHandler.Connection.GetBytes(value, out format, out maxsize);
8310 Debug.Assert(null != udtVal, "GetBytes returned null instance. Make sure that it always returns non-null value");
8311 size = udtVal.Length;
8313 //it may be legitimate, but we dont support it yet
8314 if (size < 0 || (size >= UInt16.MaxValue && maxsize != -1))
8315 throw new IndexOutOfRangeException();
8318 //if this is NULL value, write special null value
8319 byte[] lenBytes = BitConverter.GetBytes((Int64)size);
8321 if (ADP.IsEmpty(param.UdtTypeName))
8322 throw SQL.MustSetUdtTypeNameForUdtParams();
8324 // Split the input name. TypeName is returned as single 3 part name during DeriveParameters.
8325 // NOTE: ParseUdtTypeName throws if format is incorrect
8326 String[] names = SqlParameter.ParseTypeName(param.UdtTypeName, true /* is UdtTypeName */);
8327 if (!ADP.IsEmpty(names[0]) && TdsEnums.MAX_SERVERNAME < names[0].Length) {
8328 throw ADP.ArgumentOutOfRange("names");
8330 if (!ADP.IsEmpty(names[1]) && TdsEnums.MAX_SERVERNAME < names[names.Length - 2].Length) {
8331 throw ADP.ArgumentOutOfRange("names");
8333 if (TdsEnums.MAX_SERVERNAME < names[2].Length) {
8334 throw ADP.ArgumentOutOfRange("names");
8337 WriteUDTMetaData(value, names[0], names[1], names[2], stateObj);
8340 if (!isNull) {
8341 WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length
8342 if (udtVal.Length > 0) { // Only write chunk length if its value is greater than 0
8343 WriteInt(udtVal.Length, stateObj); // Chunk length
8344 stateObj.WriteByteArray(udtVal, udtVal.Length, 0); // Value
8346 WriteInt(0, stateObj); // Terminator
8348 else {
8349 WriteUnsignedLong(TdsEnums.SQL_PLP_NULL, stateObj); // PLP Null.
8351 continue; // End of UDT - continue to next parameter.
8354 else if (mt.IsPlp) {
8355 if (mt.SqlDbType != SqlDbType.Xml)
8356 WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj);
8358 else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) { // Time, Date, DateTime2, DateTimeoffset do not have the size written out
8359 maxsize = (size > actualSize) ? size : actualSize;
8360 if (maxsize == 0 && IsYukonOrNewer) {
8361 // Yukon doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322)
8362 if (mt.IsNCharType)
8363 maxsize = 2;
8364 else
8365 maxsize = 1;
8368 WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj);
8372 // scale and precision are only relevant for numeric and decimal types
8373 if (mt.SqlDbType == SqlDbType.Decimal) {
8374 if (0 == precision) {
8375 if (_isShiloh)
8376 stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION);
8377 else
8378 stateObj.WriteByte(TdsEnums.SPHINX_DEFAULT_NUMERIC_PRECISION);
8380 else
8381 stateObj.WriteByte(precision);
8383 stateObj.WriteByte(scale);
8385 else if (mt.IsVarTime) {
8386 stateObj.WriteByte(param.GetActualScale());
8389 // write out collation or xml metadata
8391 if (_isYukon && (mt.SqlDbType == SqlDbType.Xml)) {
8392 if (((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) ||
8393 ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) ||
8394 ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty))) {
8395 stateObj.WriteByte(1); //Schema present flag
8397 if ((param.XmlSchemaCollectionDatabase != null) && (param.XmlSchemaCollectionDatabase != ADP.StrEmpty)) {
8398 tempLen = (param.XmlSchemaCollectionDatabase).Length;
8399 stateObj.WriteByte((byte)(tempLen));
8400 WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj);
8402 else {
8403 stateObj.WriteByte(0); // No dbname
8406 if ((param.XmlSchemaCollectionOwningSchema != null) && (param.XmlSchemaCollectionOwningSchema != ADP.StrEmpty)) {
8407 tempLen = (param.XmlSchemaCollectionOwningSchema).Length;
8408 stateObj.WriteByte((byte)(tempLen));
8409 WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj);
8411 else {
8412 stateObj.WriteByte(0); // no xml schema name
8414 if ((param.XmlSchemaCollectionName != null) && (param.XmlSchemaCollectionName != ADP.StrEmpty)) {
8415 tempLen = (param.XmlSchemaCollectionName).Length;
8416 WriteShort((short)(tempLen), stateObj);
8417 WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj);
8419 else {
8420 WriteShort(0, stateObj); // No xml schema collection name
8424 else {
8425 stateObj.WriteByte(0); // No schema
8428 else if (_isShiloh && mt.IsCharType) {
8429 // if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter
8430 SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation;
8431 Debug.Assert(_defaultCollation != null, "_defaultCollation is null!");
8433 WriteUnsignedInt(outCollation.info, stateObj);
8434 stateObj.WriteByte(outCollation.sortId);
8437 if (0 == codePageByteSize)
8438 WriteParameterVarLen(mt, actualSize, isNull, stateObj, isDataFeed);
8439 else
8440 WriteParameterVarLen(mt, codePageByteSize, isNull, stateObj, isDataFeed);
8442 Task writeParamTask = null;
8443 // write the value now
8444 if (!isNull) {
8445 if (isSqlVal) {
8446 writeParamTask = WriteSqlValue(value, mt, actualSize, codePageByteSize, param.Offset, stateObj);
8448 else {
8449 // for codePageEncoded types, WriteValue simply expects the number of characters
8450 // For plp types, we also need the encoded byte size
8451 writeParamTask = WriteValue(value, mt, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed);
8455 // Send encryption metadata for encrypted parameters.
8456 if (isParameterEncrypted) {
8457 writeParamTask = WriteEncryptionMetadata(writeParamTask, encryptedParameterInfoToWrite, stateObj);
8460 if (!sync) {
8461 if (writeParamTask == null) {
8462 writeParamTask = stateObj.WaitForAccumulatedWrites();
8465 if (writeParamTask != null) {
8466 Task task = null;
8467 if (completion == null) {
8468 completion = new TaskCompletionSource<object>();
8469 task = completion.Task;
8472 AsyncHelper.ContinueTask(writeParamTask, completion,
8473 () => TdsExecuteRPC(cmd, rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion,
8474 startRpc: ii, startParam: i + 1),
8475 connectionToDoom: _connHandler,
8476 onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj));
8478 // Take care of releasing the locks
8479 if (releaseConnectionLock) {
8480 task.ContinueWith(_ => {
8481 _connHandler._parserLock.Release();
8482 }, TaskScheduler.Default);
8483 releaseConnectionLock = false;
8486 return task;
8489 #if DEBUG
8490 else {
8491 Debug.Assert(writeParamTask == null, "Should not have a task when executing sync");
8493 #endif
8494 } // parameter for loop
8496 // If this is not the last RPC we are sending, add the batch flag
8497 if (ii < (rpcArray.Length - 1)) {
8498 if (_isYukon) {
8499 stateObj.WriteByte(TdsEnums.YUKON_RPCBATCHFLAG);
8502 else {
8503 stateObj.WriteByte(TdsEnums.SHILOH_RPCBATCHFLAG);
8506 } // rpc for loop
8508 Task execFlushTask = stateObj.ExecuteFlush();
8509 Debug.Assert(!sync || execFlushTask == null, "Should not get a task when executing sync");
8510 if (execFlushTask != null) {
8511 Task task = null;
8513 if (completion == null) {
8514 completion = new TaskCompletionSource<object>();
8515 task = completion.Task;
8518 bool taskReleaseConnectionLock = releaseConnectionLock;
8519 execFlushTask.ContinueWith(tsk => ExecuteFlushTaskCallback(tsk, stateObj, completion, taskReleaseConnectionLock), TaskScheduler.Default);
8521 // ExecuteFlushTaskCallback will take care of the locks for us
8522 releaseConnectionLock = false;
8524 return task;
8527 catch (Exception e) {
8529 if (!ADP.IsCatchableExceptionType(e)) {
8530 throw;
8533 FailureCleanup(stateObj, e);
8535 throw;
8537 FinalizeExecuteRPC(stateObj);
8538 if (completion != null) {
8539 completion.SetResult(null);
8541 return null;
8543 catch (Exception e) {
8544 FinalizeExecuteRPC(stateObj);
8545 if (completion != null) {
8546 completion.SetException(e);
8547 return null;
8549 else {
8550 throw e;
8553 finally {
8554 Debug.Assert(firstCall || !releaseConnectionLock, "Shouldn't be releasing locks synchronously after the first call");
8555 if (releaseConnectionLock) {
8556 _connHandler._parserLock.Release();
8561 private void FinalizeExecuteRPC(TdsParserStateObject stateObj) {
8562 stateObj.SniContext = SniContext.Snix_Read;
8563 _asyncWrite = false;
8566 private void TdsExecuteRPC_OnFailure(Exception exc, TdsParserStateObject stateObj) {
8567 RuntimeHelpers.PrepareConstrainedRegions();
8568 try {
8569 #if DEBUG
8570 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
8572 RuntimeHelpers.PrepareConstrainedRegions();
8573 try {
8574 tdsReliabilitySection.Start();
8575 #else
8577 #endif //DEBUG
8578 FailureCleanup(stateObj, exc);
8580 #if DEBUG
8581 finally {
8582 tdsReliabilitySection.Stop();
8584 #endif //DEBUG
8586 catch (System.OutOfMemoryException) {
8587 _connHandler.DoomThisConnection();
8588 throw;
8590 catch (System.StackOverflowException) {
8591 _connHandler.DoomThisConnection();
8592 throw;
8594 catch (System.Threading.ThreadAbortException) {
8595 _connHandler.DoomThisConnection();
8596 throw;
8600 private void ExecuteFlushTaskCallback(Task tsk, TdsParserStateObject stateObj, TaskCompletionSource<object> completion, bool releaseConnectionLock) {
8601 try {
8602 FinalizeExecuteRPC(stateObj);
8603 if (tsk.Exception != null) {
8604 Exception exc = tsk.Exception.InnerException;
8605 RuntimeHelpers.PrepareConstrainedRegions();
8606 try {
8607 #if DEBUG
8608 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
8610 RuntimeHelpers.PrepareConstrainedRegions();
8611 try {
8612 tdsReliabilitySection.Start();
8613 #else
8615 #endif //DEBUG
8616 FailureCleanup(stateObj, tsk.Exception);
8618 #if DEBUG
8619 finally {
8620 tdsReliabilitySection.Stop();
8622 #endif //DEBUG
8624 catch (System.OutOfMemoryException e) {
8625 _connHandler.DoomThisConnection();
8626 completion.SetException(e);
8627 throw;
8629 catch (System.StackOverflowException e) {
8630 _connHandler.DoomThisConnection();
8631 completion.SetException(e);
8632 throw;
8634 catch (System.Threading.ThreadAbortException e) {
8635 _connHandler.DoomThisConnection();
8636 completion.SetException(e);
8637 throw;
8639 catch (Exception e) {
8640 exc = e;
8642 completion.SetException(exc);
8644 else {
8645 completion.SetResult(null);
8648 finally {
8649 if (releaseConnectionLock) {
8650 _connHandler._parserLock.Release();
8656 private void WriteParameterName(string parameterName, TdsParserStateObject stateObj) {
8657 // paramLen
8658 // paramName
8659 if (!ADP.IsEmpty(parameterName)) {
8660 Debug.Assert(parameterName.Length <= 0xff, "parameter name can only be 255 bytes, shouldn't get to TdsParser!");
8661 int tempLen = parameterName.Length & 0xff;
8662 stateObj.WriteByte((byte)tempLen);
8663 WriteString(parameterName, tempLen, 0, stateObj);
8665 else {
8666 stateObj.WriteByte(0);
8670 private static readonly IEnumerable<MSS.SqlDataRecord> __tvpEmptyValue = new List<MSS.SqlDataRecord>().AsReadOnly();
8671 private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefault, TdsParserStateObject stateObj) {
8673 // Determine Metadata
8675 ParameterPeekAheadValue peekAhead;
8676 MSS.SmiParameterMetaData metaData = param.MetaDataForSmi(out peekAhead);
8678 if (!_isKatmai) {
8679 MetaType mt = MetaType.GetMetaTypeFromSqlDbType(metaData.SqlDbType, metaData.IsMultiValued);
8680 throw ADP.VersionDoesNotSupportDataType(mt.TypeName);
8684 // Determine value to send
8686 object value;
8687 MSS.ExtendedClrTypeCode typeCode;
8689 // if we have an output or default param, set the value to null so we do not send it across to the server
8690 if (sendDefault) {
8691 // Value for TVP default is empty list, not NULL
8692 if (SqlDbType.Structured == metaData.SqlDbType && metaData.IsMultiValued) {
8693 value = __tvpEmptyValue;
8694 typeCode = MSS.ExtendedClrTypeCode.IEnumerableOfSqlDataRecord;
8696 else {
8697 // Need to send null value for default
8698 value = null;
8699 typeCode = MSS.ExtendedClrTypeCode.DBNull;
8702 else if (param.Direction == ParameterDirection.Output) {
8703 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.
8704 param.Value = null;
8705 value = null;
8706 typeCode = MSS.ExtendedClrTypeCode.DBNull;
8707 param.ParamaterIsSqlType = isCLRType;
8709 else {
8710 value = param.GetCoercedValue();
8711 typeCode = MSS.MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType(
8712 metaData.SqlDbType, metaData.IsMultiValued, value, null, MSS.SmiContextFactory.KatmaiVersion);
8715 if (Bid.AdvancedOn) {
8716 Bid.Trace("<sc.TdsParser.WriteSmiParameter|ADV> %d#, Sending parameter '%ls', default flag=%d, metadata:\n", ObjectID, param.ParameterName, sendDefault?1:0);
8717 Bid.PutStr(metaData.TraceString(3));
8718 Bid.Trace("\n");
8722 // Write parameter metadata
8724 WriteSmiParameterMetaData(metaData, sendDefault, stateObj);
8727 // Now write the value
8729 TdsParameterSetter paramSetter = new TdsParameterSetter(stateObj, metaData);
8730 MSS.ValueUtilsSmi.SetCompatibleValueV200(
8731 new MSS.SmiEventSink_Default(), // TDS Errors/events dealt with at lower level for now, just need an object for processing
8732 paramSetter,
8733 0, // ordinal. TdsParameterSetter only handles one parameter at a time
8734 metaData,
8735 value,
8736 typeCode,
8737 param.Offset,
8738 0 < param.Size ? param.Size : -1,
8739 peekAhead);
8742 // Writes metadata portion of parameter stream from an SmiParameterMetaData object.
8743 private void WriteSmiParameterMetaData(MSS.SmiParameterMetaData metaData, bool sendDefault, TdsParserStateObject stateObj) {
8744 // Determine status
8745 byte status = 0;
8746 if (ParameterDirection.Output == metaData.Direction || ParameterDirection.InputOutput == metaData.Direction) {
8747 status |= TdsEnums.RPC_PARAM_BYREF;
8750 if (sendDefault) {
8751 status |= TdsEnums.RPC_PARAM_DEFAULT;
8754 // Write everything out
8755 WriteParameterName(metaData.Name, stateObj);
8756 stateObj.WriteByte(status);
8757 WriteSmiTypeInfo(metaData, stateObj);
8760 // Write a TypeInfo stream
8761 // Devnote: we remap the legacy types (text, ntext, and image) to SQLBIGVARCHAR, SQLNVARCHAR, and SQLBIGVARBINARY
8762 private void WriteSmiTypeInfo(MSS.SmiExtendedMetaData metaData, TdsParserStateObject stateObj) {
8763 switch(metaData.SqlDbType) {
8764 case SqlDbType.BigInt:
8765 stateObj.WriteByte(TdsEnums.SQLINTN);
8766 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8767 break;
8768 case SqlDbType.Binary:
8769 stateObj.WriteByte(TdsEnums.SQLBIGBINARY);
8770 WriteUnsignedShort(checked((ushort)metaData.MaxLength), stateObj);
8771 break;
8772 case SqlDbType.Bit:
8773 stateObj.WriteByte(TdsEnums.SQLBITN);
8774 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8775 break;
8776 case SqlDbType.Char:
8777 stateObj.WriteByte(TdsEnums.SQLBIGCHAR);
8778 WriteUnsignedShort(checked((ushort)(metaData.MaxLength)), stateObj);
8779 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8780 stateObj.WriteByte(_defaultCollation.sortId);
8781 break;
8782 case SqlDbType.DateTime:
8783 stateObj.WriteByte(TdsEnums.SQLDATETIMN);
8784 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8785 break;
8786 case SqlDbType.Decimal:
8787 stateObj.WriteByte(TdsEnums.SQLNUMERICN);
8788 stateObj.WriteByte(checked((byte)MetaType.MetaDecimal.FixedLength)); // SmiMetaData's length and actual wire format's length are different
8789 stateObj.WriteByte(0 == metaData.Precision ? (byte)1 : metaData.Precision);
8790 stateObj.WriteByte(metaData.Scale);
8791 break;
8792 case SqlDbType.Float:
8793 stateObj.WriteByte(TdsEnums.SQLFLTN);
8794 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8795 break;
8796 case SqlDbType.Image:
8797 stateObj.WriteByte(TdsEnums.SQLBIGVARBINARY);
8798 WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj);
8799 break;
8800 case SqlDbType.Int:
8801 stateObj.WriteByte(TdsEnums.SQLINTN);
8802 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8803 break;
8804 case SqlDbType.Money:
8805 stateObj.WriteByte(TdsEnums.SQLMONEYN);
8806 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8807 break;
8808 case SqlDbType.NChar:
8809 stateObj.WriteByte(TdsEnums.SQLNCHAR);
8810 WriteUnsignedShort(checked((ushort)(metaData.MaxLength*2)), stateObj);
8811 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8812 stateObj.WriteByte(_defaultCollation.sortId);
8813 break;
8814 case SqlDbType.NText:
8815 stateObj.WriteByte(TdsEnums.SQLNVARCHAR);
8816 WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj);
8817 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8818 stateObj.WriteByte(_defaultCollation.sortId);
8819 break;
8820 case SqlDbType.NVarChar:
8821 stateObj.WriteByte(TdsEnums.SQLNVARCHAR);
8822 if (MSS.SmiMetaData.UnlimitedMaxLengthIndicator == metaData.MaxLength) {
8823 WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj);
8825 else {
8826 WriteUnsignedShort(checked((ushort)(metaData.MaxLength*2)), stateObj);
8828 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8829 stateObj.WriteByte(_defaultCollation.sortId);
8830 break;
8831 case SqlDbType.Real:
8832 stateObj.WriteByte(TdsEnums.SQLFLTN);
8833 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8834 break;
8835 case SqlDbType.UniqueIdentifier:
8836 stateObj.WriteByte(TdsEnums.SQLUNIQUEID);
8837 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8838 break;
8839 case SqlDbType.SmallDateTime:
8840 stateObj.WriteByte(TdsEnums.SQLDATETIMN);
8841 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8842 break;
8843 case SqlDbType.SmallInt:
8844 stateObj.WriteByte(TdsEnums.SQLINTN);
8845 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8846 break;
8847 case SqlDbType.SmallMoney:
8848 stateObj.WriteByte(TdsEnums.SQLMONEYN);
8849 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8850 break;
8851 case SqlDbType.Text:
8852 stateObj.WriteByte(TdsEnums.SQLBIGVARCHAR);
8853 WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj);
8854 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8855 stateObj.WriteByte(_defaultCollation.sortId);
8856 break;
8857 case SqlDbType.Timestamp:
8858 stateObj.WriteByte(TdsEnums.SQLBIGBINARY);
8859 WriteShort(checked((int)metaData.MaxLength), stateObj);
8860 break;
8861 case SqlDbType.TinyInt:
8862 stateObj.WriteByte(TdsEnums.SQLINTN);
8863 stateObj.WriteByte(checked((byte)metaData.MaxLength));
8864 break;
8865 case SqlDbType.VarBinary:
8866 stateObj.WriteByte(TdsEnums.SQLBIGVARBINARY);
8867 WriteUnsignedShort(unchecked((ushort)metaData.MaxLength), stateObj);
8868 break;
8869 case SqlDbType.VarChar:
8870 stateObj.WriteByte(TdsEnums.SQLBIGVARCHAR);
8871 WriteUnsignedShort(unchecked((ushort)metaData.MaxLength), stateObj);
8872 WriteUnsignedInt(_defaultCollation.info, stateObj); //
8873 stateObj.WriteByte(_defaultCollation.sortId);
8874 break;
8875 case SqlDbType.Variant:
8876 stateObj.WriteByte(TdsEnums.SQLVARIANT);
8877 WriteInt(checked((int)metaData.MaxLength), stateObj);
8878 break;
8879 case SqlDbType.Xml:
8880 stateObj.WriteByte(TdsEnums.SQLXMLTYPE);
8881 // Is there a schema
8882 if (ADP.IsEmpty(metaData.TypeSpecificNamePart1) && ADP.IsEmpty(metaData.TypeSpecificNamePart2) &&
8883 ADP.IsEmpty(metaData.TypeSpecificNamePart3)) {
8884 stateObj.WriteByte(0); // schema not present
8886 else {
8887 stateObj.WriteByte(1); // schema present
8888 WriteIdentifier(metaData.TypeSpecificNamePart1, stateObj);
8889 WriteIdentifier(metaData.TypeSpecificNamePart2, stateObj);
8890 WriteIdentifierWithShortLength(metaData.TypeSpecificNamePart3, stateObj);
8892 break;
8893 case SqlDbType.Udt:
8894 stateObj.WriteByte(TdsEnums.SQLUDT);
8895 WriteIdentifier(metaData.TypeSpecificNamePart1, stateObj);
8896 WriteIdentifier(metaData.TypeSpecificNamePart2, stateObj);
8897 WriteIdentifier(metaData.TypeSpecificNamePart3, stateObj);
8898 break;
8899 case SqlDbType.Structured:
8900 if (metaData.IsMultiValued) {
8901 WriteTvpTypeInfo(metaData, stateObj);
8903 else {
8904 Debug.Assert(false, "SUDTs not yet supported.");
8906 break;
8907 case SqlDbType.Date:
8908 stateObj.WriteByte(TdsEnums.SQLDATE);
8909 break;
8910 case SqlDbType.Time:
8911 stateObj.WriteByte(TdsEnums.SQLTIME);
8912 stateObj.WriteByte(metaData.Scale);
8913 break;
8914 case SqlDbType.DateTime2:
8915 stateObj.WriteByte(TdsEnums.SQLDATETIME2);
8916 stateObj.WriteByte(metaData.Scale);
8917 break;
8918 case SqlDbType.DateTimeOffset:
8919 stateObj.WriteByte(TdsEnums.SQLDATETIMEOFFSET);
8920 stateObj.WriteByte(metaData.Scale);
8921 break;
8922 default:
8923 Debug.Assert(false, "Unknown SqlDbType should have been caught earlier!");
8924 break;
8928 private void WriteTvpTypeInfo(MSS.SmiExtendedMetaData metaData, TdsParserStateObject stateObj) {
8929 Debug.Assert(SqlDbType.Structured == metaData.SqlDbType && metaData.IsMultiValued,
8930 "Invalid metadata for TVPs. Type=" + metaData.SqlDbType);
8931 // Type token
8932 stateObj.WriteByte((byte)TdsEnums.SQLTABLE);
8934 // 3-part name (DB, Schema, TypeName)
8935 WriteIdentifier(metaData.TypeSpecificNamePart1, stateObj);
8936 WriteIdentifier(metaData.TypeSpecificNamePart2, stateObj);
8937 WriteIdentifier(metaData.TypeSpecificNamePart3, stateObj);
8939 // TVP_COLMETADATA
8940 if (0 == metaData.FieldMetaData.Count) {
8941 WriteUnsignedShort((ushort)TdsEnums.TVP_NOMETADATA_TOKEN, stateObj);
8943 else {
8944 // COUNT of columns
8945 WriteUnsignedShort(checked((ushort) metaData.FieldMetaData.Count), stateObj);
8947 // TvpColumnMetaData for each column (look for defaults in this loop
8948 MSS.SmiDefaultFieldsProperty defaults = (MSS.SmiDefaultFieldsProperty) metaData.ExtendedProperties[MSS.SmiPropertySelector.DefaultFields];
8949 for(int i=0; i<metaData.FieldMetaData.Count; i++) {
8950 WriteTvpColumnMetaData(metaData.FieldMetaData[i], defaults[i], stateObj);
8953 // optional OrderUnique metadata
8954 WriteTvpOrderUnique(metaData, stateObj);
8958 // END of optional metadata
8959 stateObj.WriteByte(TdsEnums.TVP_END_TOKEN);
8962 // Write a single TvpColumnMetaData stream to the server
8963 private void WriteTvpColumnMetaData(MSS.SmiExtendedMetaData md, bool isDefault, TdsParserStateObject stateObj) {
8964 // User Type
8965 if (SqlDbType.Timestamp == md.SqlDbType) {
8966 WriteUnsignedInt(TdsEnums.SQLTIMESTAMP, stateObj);
8967 } else {
8968 WriteUnsignedInt(0, stateObj);
8971 // Flags
8972 ushort status = TdsEnums.Nullable;
8973 if (isDefault) {
8974 status |= TdsEnums.TVP_DEFAULT_COLUMN;
8976 WriteUnsignedShort(status, stateObj);
8978 // Type info
8979 WriteSmiTypeInfo(md, stateObj);
8981 // Column name
8982 // per spec, "ColName is never sent to server or client for TVP, it is required within a TVP to be zero length."
8983 WriteIdentifier(null, stateObj);
8986 // temporary-results structure used only by WriteTvpOrderUnique
8987 // use class to avoid List<T>'s per-struct-instantiated memory costs.
8988 private class TdsOrderUnique {
8989 internal short ColumnOrdinal;
8990 internal byte Flags;
8992 internal TdsOrderUnique(short ordinal, byte flags) {
8993 ColumnOrdinal = ordinal;
8994 Flags = flags;
8998 private void WriteTvpOrderUnique(MSS.SmiExtendedMetaData metaData, TdsParserStateObject stateObj) {
8999 // TVP_ORDER_UNIQUE token (uniqueness and sort order)
9001 // Merge order and unique keys into a single token stream
9003 MSS.SmiOrderProperty orderProperty = (MSS.SmiOrderProperty) metaData.ExtendedProperties[MSS.SmiPropertySelector.SortOrder];
9004 MSS.SmiUniqueKeyProperty uniqueKeyProperty = (MSS.SmiUniqueKeyProperty) metaData.ExtendedProperties[MSS.SmiPropertySelector.UniqueKey];
9006 // Build list from
9007 List<TdsOrderUnique> columnList = new List<TdsOrderUnique>(metaData.FieldMetaData.Count);
9008 for(int i=0; i<metaData.FieldMetaData.Count; i++) {
9010 // Add appropriate SortOrder flag
9011 byte flags = 0;
9012 MSS.SmiOrderProperty.SmiColumnOrder columnOrder = orderProperty[i];
9013 if (SortOrder.Ascending == columnOrder.Order) {
9014 flags = TdsEnums.TVP_ORDERASC_FLAG;
9016 else if (SortOrder.Descending == columnOrder.Order) {
9017 flags = TdsEnums.TVP_ORDERDESC_FLAG;
9020 // Add unique key flage if appropriate
9021 if (uniqueKeyProperty[i]) {
9022 flags |= TdsEnums.TVP_UNIQUE_FLAG;
9025 // Remember this column if any flags were set
9026 if (0 != flags) {
9027 columnList.Add(new TdsOrderUnique(checked((short)(i+1)), flags));
9031 // Write flagged columns to wire...
9032 if (0 < columnList.Count) {
9033 stateObj.WriteByte(TdsEnums.TVP_ORDER_UNIQUE_TOKEN);
9034 WriteShort(columnList.Count, stateObj);
9035 foreach(TdsOrderUnique column in columnList) {
9036 WriteShort(column.ColumnOrdinal, stateObj);
9037 stateObj.WriteByte(column.Flags);
9042 internal Task WriteBulkCopyDone(TdsParserStateObject stateObj) {
9043 // Write DONE packet
9045 if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) {
9046 throw ADP.ClosedConnectionError();
9048 stateObj.WriteByte(TdsEnums.SQLDONE);
9049 WriteShort(0, stateObj);
9050 WriteShort(0, stateObj);
9051 WriteInt(0, stateObj);
9053 stateObj._pendingData = true;
9054 stateObj._messageStatus = 0;
9055 return stateObj.WritePacket(TdsEnums.HARDFLUSH);
9058 /// <summary>
9059 /// Loads the column encryptions keys into cache. This will read the master key info,
9060 /// decrypt the CEK and keep it ready for encryption.
9061 /// </summary>
9062 /// <returns></returns>
9063 internal void LoadColumnEncryptionKeys (_SqlMetaDataSet metadataCollection, string serverName) {
9064 if (_serverSupportsColumnEncryption && ShouldEncryptValuesForBulkCopy()) {
9065 for (int col = 0; col < metadataCollection.Length; col++) {
9066 if (null != metadataCollection[col]) {
9067 _SqlMetaData md = metadataCollection[col];
9068 if (md.isEncrypted) {
9069 SqlSecurityUtility.DecryptSymmetricKey(md.cipherMD, serverName);
9076 /// <summary>
9077 /// Writes a single entry of CEK Table into TDS Stream (for bulk copy).
9078 /// </summary>
9079 /// <returns></returns>
9080 internal void WriteEncryptionEntries (ref SqlTceCipherInfoTable cekTable, TdsParserStateObject stateObj) {
9081 for (int i =0; i < cekTable.Size; i++) {
9082 // Write Db ID
9083 WriteInt(cekTable[i].DatabaseId, stateObj);
9085 // Write Key ID
9086 WriteInt(cekTable[i].CekId, stateObj);
9088 // Write Key Version
9089 WriteInt(cekTable[i].CekVersion, stateObj);
9091 // Write 8 bytes of key MD Version
9092 Debug.Assert (8 == cekTable[i].CekMdVersion.Length);
9093 stateObj.WriteByteArray (cekTable[i].CekMdVersion, 8, 0);
9095 // We don't really need to send the keys
9096 stateObj.WriteByte(0x00);
9100 /// <summary>
9101 /// Writes a CEK Table (as part of COLMETADATA token) for bulk copy.
9102 /// </summary>
9103 /// <returns></returns>
9104 internal void WriteCekTable (_SqlMetaDataSet metadataCollection, TdsParserStateObject stateObj) {
9105 if (!_serverSupportsColumnEncryption) {
9106 return;
9109 // If no cek table is present, send a count of 0 for table size
9110 // Note- Cek table (with 0 entries) will be present if TCE
9111 // was enabled and server supports it!
9112 // OR if encryption was disabled in connection options
9113 if (!metadataCollection.cekTable.HasValue ||
9114 !ShouldEncryptValuesForBulkCopy()) {
9115 WriteShort(0x00, stateObj);
9116 return;
9119 SqlTceCipherInfoTable cekTable = metadataCollection.cekTable.Value;
9120 ushort count = (ushort)cekTable.Size;
9122 WriteShort(count, stateObj);
9124 WriteEncryptionEntries(ref cekTable, stateObj);
9127 /// <summary>
9128 /// Writes the UserType and TYPE_INFO values for CryptoMetadata (for bulk copy).
9129 /// </summary>
9130 /// <returns></returns>
9131 internal void WriteTceUserTypeAndTypeInfo(SqlMetaDataPriv mdPriv, TdsParserStateObject stateObj) {
9132 // Write the UserType (4 byte value)
9133 WriteInt(0x0, stateObj); //
9135 Debug.Assert(SqlDbType.Xml != mdPriv.type);
9136 Debug.Assert(SqlDbType.Udt != mdPriv.type);
9138 stateObj.WriteByte(mdPriv.tdsType);
9140 switch (mdPriv.type) {
9141 case SqlDbType.Decimal:
9142 WriteTokenLength(mdPriv.tdsType, mdPriv.length, stateObj);
9143 stateObj.WriteByte(mdPriv.precision);
9144 stateObj.WriteByte(mdPriv.scale);
9145 break;
9146 case SqlDbType.Date:
9147 // Nothing more to write!
9148 break;
9149 case SqlDbType.Time:
9150 case SqlDbType.DateTime2:
9151 case SqlDbType.DateTimeOffset:
9152 stateObj.WriteByte(mdPriv.scale);
9153 break;
9154 default:
9155 WriteTokenLength(mdPriv.tdsType, mdPriv.length, stateObj);
9156 if (mdPriv.metaType.IsCharType && _isShiloh) {
9157 WriteUnsignedInt(mdPriv.collation.info, stateObj);
9158 stateObj.WriteByte(mdPriv.collation.sortId);
9160 break;
9164 /// <summary>
9165 /// Writes the crypto metadata (as part of COLMETADATA token) for encrypted columns.
9166 /// </summary>
9167 /// <returns></returns>
9168 internal void WriteCryptoMetadata(_SqlMetaData md, TdsParserStateObject stateObj) {
9169 if (!_serverSupportsColumnEncryption || // TCE Feature supported
9170 !md.isEncrypted || // Column is not encrypted
9171 !ShouldEncryptValuesForBulkCopy()) { // TCE disabled on connection string
9172 return;
9175 // Write the ordinal
9176 WriteShort (md.cipherMD.CekTableOrdinal, stateObj);
9178 // Write UserType and TYPEINFO
9179 WriteTceUserTypeAndTypeInfo(md.baseTI, stateObj);
9181 // Write Encryption Algo
9182 stateObj.WriteByte(md.cipherMD.CipherAlgorithmId);
9184 if (TdsEnums.CustomCipherAlgorithmId == md.cipherMD.CipherAlgorithmId) {
9185 // Write the algorithm name
9186 Debug.Assert (md.cipherMD.CipherAlgorithmName.Length < 256);
9187 stateObj.WriteByte((byte)md.cipherMD.CipherAlgorithmName.Length);
9188 WriteString(md.cipherMD.CipherAlgorithmName, stateObj);
9191 // Write Encryption Algo Type
9192 stateObj.WriteByte(md.cipherMD.EncryptionType);
9194 // Write Normalization Version
9195 stateObj.WriteByte(md.cipherMD.NormalizationRuleVersion);
9198 internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int count, TdsParserStateObject stateObj) {
9199 if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) {
9200 throw ADP.ClosedConnectionError();
9203 stateObj.WriteByte(TdsEnums.SQLCOLMETADATA);
9204 WriteShort(count, stateObj);
9206 // Write CEK table - 0 count
9207 WriteCekTable(metadataCollection, stateObj);
9209 for (int i = 0; i < metadataCollection.Length; i++) {
9210 if (metadataCollection[i] != null) {
9211 _SqlMetaData md = metadataCollection[i];
9213 // read user type - 4 bytes Yukon, 2 backwards
9214 if (IsYukonOrNewer) {
9215 WriteInt(0x0, stateObj);
9217 else {
9218 WriteShort(0x0000, stateObj);
9221 // Write the flags
9222 UInt16 flags;
9223 flags = (UInt16)(md.updatability << 2);
9224 flags |= (UInt16)(md.isNullable ? (UInt16)TdsEnums.Nullable : (UInt16)0);
9225 flags |= (UInt16)(md.isIdentity ? (UInt16)TdsEnums.Identity : (UInt16)0);
9227 // Write the next byte of flags
9228 if (_serverSupportsColumnEncryption) { // TCE Supported
9229 if (ShouldEncryptValuesForBulkCopy()) { // TCE enabled on connection options
9230 flags |= (UInt16)(md.isEncrypted ? (UInt16)(TdsEnums.IsEncrypted << 8) : (UInt16)0);
9234 WriteShort(flags, stateObj);// write the flags
9236 // todo:
9237 // for xml WriteTokenLength results in a no-op
9238 // discuss this with blaine ...
9239 // (Microsoft) xml datatype does not have token length in its metadata. So it should be a noop.
9241 switch (md.type) {
9242 case SqlDbType.Decimal:
9243 stateObj.WriteByte(md.tdsType);
9244 WriteTokenLength(md.tdsType, md.length, stateObj);
9245 stateObj.WriteByte(md.precision);
9246 stateObj.WriteByte(md.scale);
9247 break;
9248 case SqlDbType.Xml:
9250 stateObj.WriteByteArray(s_xmlMetadataSubstituteSequence, s_xmlMetadataSubstituteSequence.Length, 0);
9251 break;
9252 case SqlDbType.Udt:
9253 stateObj.WriteByte(TdsEnums.SQLBIGVARBINARY);
9254 WriteTokenLength(TdsEnums.SQLBIGVARBINARY, md.length, stateObj);
9255 break;
9256 case SqlDbType.Date:
9257 stateObj.WriteByte(md.tdsType);
9258 break;
9259 case SqlDbType.Time:
9260 case SqlDbType.DateTime2:
9261 case SqlDbType.DateTimeOffset:
9262 stateObj.WriteByte(md.tdsType);
9263 stateObj.WriteByte(md.scale);
9264 break;
9265 default:
9266 stateObj.WriteByte(md.tdsType);
9267 WriteTokenLength(md.tdsType, md.length, stateObj);
9268 if (md.metaType.IsCharType && _isShiloh) {
9269 WriteUnsignedInt(md.collation.info, stateObj);
9270 stateObj.WriteByte(md.collation.sortId);
9272 break;
9275 if (md.metaType.IsLong && !md.metaType.IsPlp) {
9276 WriteShort(md.tableName.Length, stateObj);
9277 WriteString(md.tableName, stateObj);
9280 WriteCryptoMetadata(md, stateObj);
9282 stateObj.WriteByte((byte)md.column.Length);
9283 WriteString(md.column, stateObj);
9285 } // end for loop
9288 /// <summary>
9289 /// Determines if a column value should be encrypted when using BulkCopy (based on connectionstring setting).
9290 /// </summary>
9291 /// <returns></returns>
9292 internal bool ShouldEncryptValuesForBulkCopy () {
9293 if (null != _connHandler &&
9294 null != _connHandler.ConnectionOptions &&
9295 SqlConnectionColumnEncryptionSetting.Enabled == _connHandler.ConnectionOptions.ColumnEncryptionSetting) {
9296 return true;
9299 return false;
9302 /// <summary>
9303 /// Encrypts a column value (for SqlBulkCopy)
9304 /// </summary>
9305 /// <returns></returns>
9306 internal object EncryptColumnValue (object value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) {
9307 Debug.Assert (_serverSupportsColumnEncryption, "Server doesn't support encryption, yet we received encryption metadata");
9308 Debug.Assert (ShouldEncryptValuesForBulkCopy(), "Encryption attempted when not requested");
9310 if (isDataFeed) { // can't encrypt a stream column
9311 SQL.StreamNotSupportOnEncryptedColumn(column);
9314 int actualLengthInBytes;
9315 switch(metadata.baseTI.metaType.NullableType) {
9316 case TdsEnums.SQLBIGBINARY:
9317 case TdsEnums.SQLBIGVARBINARY:
9318 case TdsEnums.SQLIMAGE:
9319 // For some datatypes, engine does truncation before storing the value. (For example, when
9320 // trying to insert a varbinary(7000) into a varbinary(3000) column). Since we encrypt the
9321 // column values, engine has no way to tell the size of the plaintext datatype. Therefore,
9322 // we truncate the values based on target column sizes here before encrypting them. This
9323 // truncation is only needed if we exceed the max column length or if the target column is
9324 // not a blob type (eg. varbinary(max)). The actual work of truncating the column happens
9325 // when we normalize and serialize the data buffers. The serialization routine expects us
9326 // to report the size of data to be copied out (for serialization). If we underreport the
9327 // size, truncation will happen for us!
9328 actualLengthInBytes = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length;
9329 if (metadata.baseTI.length > 0 &&
9330 actualLengthInBytes > metadata.baseTI.length) { // see comments agove
9331 actualLengthInBytes = metadata.baseTI.length;
9333 break;
9335 case TdsEnums.SQLUNIQUEID:
9336 actualLengthInBytes = GUID_SIZE; // that's a constant for guid
9337 break;
9338 case TdsEnums.SQLBIGCHAR:
9339 case TdsEnums.SQLBIGVARCHAR:
9340 case TdsEnums.SQLTEXT:
9341 if (null == _defaultEncoding)
9343 ThrowUnsupportedCollationEncountered(null); // stateObject only when reading
9346 string stringValue = (isSqlType) ? ((SqlString)value).Value : (string)value;
9347 actualLengthInBytes = _defaultEncoding.GetByteCount(stringValue);
9349 // If the string length is > max length, then use the max length (see comments above)
9350 if (metadata.baseTI.length > 0 &&
9351 actualLengthInBytes > metadata.baseTI.length) {
9352 actualLengthInBytes = metadata.baseTI.length; // this ensure truncation!
9355 break;
9356 case TdsEnums.SQLNCHAR:
9357 case TdsEnums.SQLNVARCHAR:
9358 case TdsEnums.SQLNTEXT:
9359 actualLengthInBytes = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2;
9361 if (metadata.baseTI.length > 0 &&
9362 actualLengthInBytes > metadata.baseTI.length) { // see comments above
9363 actualLengthInBytes = metadata.baseTI.length;
9366 break;
9368 default:
9369 actualLengthInBytes = metadata.baseTI.length;
9370 break;
9373 byte[] serializedValue;
9374 if (isSqlType) {
9375 // SqlType
9376 serializedValue = SerializeUnencryptedSqlValue (value,
9377 metadata.baseTI.metaType,
9378 actualLengthInBytes,
9379 offset : 0,
9380 normalizationVersion: metadata.cipherMD.NormalizationRuleVersion,
9381 stateObj: stateObj);
9383 else {
9384 serializedValue = SerializeUnencryptedValue (value,
9385 metadata.baseTI.metaType,
9386 metadata.baseTI.scale,
9387 actualLengthInBytes,
9388 offset: 0,
9389 isDataFeed: isDataFeed,
9390 normalizationVersion: metadata.cipherMD.NormalizationRuleVersion,
9391 stateObj: stateObj);
9394 Debug.Assert(serializedValue != null, "serializedValue should not be null in TdsExecuteRPC.");
9395 return SqlSecurityUtility.EncryptWithKey(
9396 serializedValue,
9397 metadata.cipherMD,
9398 _connHandler.ConnectionOptions.DataSource);
9401 internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) {
9402 Debug.Assert(!isSqlType || value is INullable, "isSqlType is true, but value can not be type cast to an INullable");
9403 Debug.Assert(!isDataFeed ^ value is DataFeed, "Incorrect value for isDataFeed");
9405 Encoding saveEncoding = _defaultEncoding;
9406 SqlCollation saveCollation = _defaultCollation;
9407 int saveCodePage = _defaultCodePage;
9408 int saveLCID = _defaultLCID;
9409 Task resultTask = null;
9410 Task internalWriteTask = null;
9412 if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) {
9413 throw ADP.ClosedConnectionError();
9415 try {
9416 if (metadata.encoding != null) {
9417 _defaultEncoding = metadata.encoding;
9419 if (metadata.collation != null) {
9420 _defaultCollation = metadata.collation;
9421 _defaultLCID = _defaultCollation.LCID;
9423 _defaultCodePage = metadata.codePage;
9425 MetaType metatype = metadata.metaType;
9426 int ccb = 0;
9427 int ccbStringBytes = 0;
9429 if (isNull) {
9430 // For UDT, remember we treat as binary even though it is a PLP
9431 if (metatype.IsPlp && (metatype.NullableType != TdsEnums.SQLUDT || metatype.IsLong)) {
9432 WriteLong(unchecked((long)TdsEnums.SQL_PLP_NULL), stateObj);
9434 else if (!metatype.IsFixed && !metatype.IsLong && !metatype.IsVarTime) {
9435 WriteShort(TdsEnums.VARNULL, stateObj);
9437 else {
9438 stateObj.WriteByte(TdsEnums.FIXEDNULL);
9440 return resultTask;
9443 if (!isDataFeed) {
9444 switch (metatype.NullableType) {
9445 case TdsEnums.SQLBIGBINARY:
9446 case TdsEnums.SQLBIGVARBINARY:
9447 case TdsEnums.SQLIMAGE:
9448 case TdsEnums.SQLUDT:
9449 ccb = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length;
9450 break;
9451 case TdsEnums.SQLUNIQUEID:
9452 ccb = GUID_SIZE; // that's a constant for guid
9453 break;
9454 case TdsEnums.SQLBIGCHAR:
9455 case TdsEnums.SQLBIGVARCHAR:
9456 case TdsEnums.SQLTEXT:
9457 if (null == _defaultEncoding) {
9458 ThrowUnsupportedCollationEncountered(null); // stateObject only when reading
9461 string stringValue = null;
9462 if (isSqlType) {
9463 stringValue = ((SqlString)value).Value;
9465 else {
9466 stringValue = (string)value;
9469 ccb = stringValue.Length;
9470 ccbStringBytes = _defaultEncoding.GetByteCount(stringValue);
9471 break;
9472 case TdsEnums.SQLNCHAR:
9473 case TdsEnums.SQLNVARCHAR:
9474 case TdsEnums.SQLNTEXT:
9475 ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2;
9476 break;
9477 case TdsEnums.SQLXMLTYPE:
9478 // Value here could be string or XmlReader
9479 if (value is XmlReader) {
9480 value = MetaType.GetStringFromXml((XmlReader)value);
9482 ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2;
9483 break;
9485 default:
9486 ccb = metadata.length;
9487 break;
9490 else {
9491 Debug.Assert(metatype.IsLong &&
9492 ((metatype.SqlDbType == SqlDbType.VarBinary && value is StreamDataFeed) ||
9493 ((metatype.SqlDbType == SqlDbType.VarChar || metatype.SqlDbType == SqlDbType.NVarChar) && value is TextDataFeed) ||
9494 (metatype.SqlDbType == SqlDbType.Xml && value is XmlDataFeed)),
9495 "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)");
9499 // Expected the text length in data stream for bulk copy of text, ntext, or image data.
9501 if (metatype.IsLong) {
9502 switch (metatype.SqlDbType) {
9503 case SqlDbType.Text:
9504 case SqlDbType.NText:
9505 case SqlDbType.Image:
9506 stateObj.WriteByteArray(s_longDataHeader, s_longDataHeader.Length, 0);
9507 WriteTokenLength(metadata.tdsType, ccbStringBytes == 0 ? ccb : ccbStringBytes, stateObj);
9508 break;
9510 case SqlDbType.VarChar:
9511 case SqlDbType.NVarChar:
9512 case SqlDbType.VarBinary:
9513 case SqlDbType.Xml:
9514 case SqlDbType.Udt:
9515 // plp data
9516 WriteUnsignedLong(TdsEnums.SQL_PLP_UNKNOWNLEN, stateObj);
9517 break;
9520 else {
9521 WriteTokenLength(metadata.tdsType, ccbStringBytes == 0 ? ccb : ccbStringBytes, stateObj);
9524 if (isSqlType) {
9525 internalWriteTask = WriteSqlValue(value, metatype, ccb, ccbStringBytes, 0, stateObj);
9527 else if (metatype.SqlDbType != SqlDbType.Udt || metatype.IsLong) {
9528 internalWriteTask = WriteValue(value, metatype, metadata.scale, ccb, ccbStringBytes, 0, stateObj, metadata.length, isDataFeed);
9529 if ((internalWriteTask == null) && (_asyncWrite)) {
9530 internalWriteTask = stateObj.WaitForAccumulatedWrites();
9532 Debug.Assert(_asyncWrite || stateObj.WaitForAccumulatedWrites() == null, "Should not have accumulated writes when writing sync");
9534 else {
9535 WriteShort(ccb, stateObj);
9536 internalWriteTask = stateObj.WriteByteArray((byte[])value, ccb, 0);
9539 #if DEBUG
9540 //In DEBUG mode, when SetAlwaysTaskOnWrite is true, we create a task. Allows us to verify async execution paths.
9541 if (_asyncWrite && internalWriteTask == null && SqlBulkCopy.SetAlwaysTaskOnWrite == true) {
9542 internalWriteTask = Task.FromResult<object>(null);
9544 #endif
9545 if (internalWriteTask != null) { //i.e. the write was async.
9546 resultTask = WriteBulkCopyValueSetupContinuation(internalWriteTask, saveEncoding, saveCollation, saveCodePage, saveLCID);
9549 finally {
9550 if (internalWriteTask == null) {
9551 _defaultEncoding = saveEncoding;
9552 _defaultCollation = saveCollation;
9553 _defaultCodePage = saveCodePage;
9554 _defaultLCID = saveLCID;
9557 return resultTask;
9560 // This is in its own method to avoid always allocating the lambda in WriteBulkCopyValue
9561 private Task WriteBulkCopyValueSetupContinuation(Task internalWriteTask, Encoding saveEncoding, SqlCollation saveCollation, int saveCodePage, int saveLCID) {
9562 return internalWriteTask.ContinueWith<Task>(t => {
9563 _defaultEncoding = saveEncoding;
9564 _defaultCollation = saveCollation;
9565 _defaultCodePage = saveCodePage;
9566 _defaultLCID = saveLCID;
9567 return t;
9568 }, TaskScheduler.Default).Unwrap();
9571 // Write mars header data, not including the mars header length
9572 private void WriteMarsHeaderData(TdsParserStateObject stateObj, SqlInternalTransaction transaction) {
9573 // Function to send over additional payload header data for Yukon and beyond only.
9574 Debug.Assert(_isYukon, "WriteMarsHeaderData called on a non-Yukon server");
9576 // These are not necessary - can have local started in distributed.
9577 // Debug.Assert(!(null != sqlTransaction && null != distributedTransaction), "Error to have local (api started) and distributed transaction at the same time!");
9578 // Debug.Assert(!(null != _userStartedLocalTransaction && null != distributedTransaction), "Error to have local (started outside of the api) and distributed transaction at the same time!");
9580 // We may need to update the mars header length if mars header is changed in the future
9582 WriteShort(TdsEnums.HEADERTYPE_MARS, stateObj);
9584 if (null != transaction && SqlInternalTransaction.NullTransactionId != transaction.TransactionId) {
9585 WriteLong(transaction.TransactionId, stateObj);
9586 WriteInt(stateObj.IncrementAndObtainOpenResultCount(transaction), stateObj);
9588 else {
9589 // If no transaction, send over retained transaction descriptor (empty if none retained)
9590 // and always 1 for result count.
9591 WriteLong(_retainedTransactionId, stateObj);
9592 WriteInt(stateObj.IncrementAndObtainOpenResultCount(null), stateObj);
9596 private int GetNotificationHeaderSize(SqlNotificationRequest notificationRequest) {
9597 if (null != notificationRequest) {
9598 string callbackId = notificationRequest.UserData;
9599 string service = notificationRequest.Options;
9600 int timeout = notificationRequest.Timeout;
9602 if (null == callbackId) {
9603 throw ADP.ArgumentNull("CallbackId");
9605 else if (UInt16.MaxValue < callbackId.Length) {
9606 throw ADP.ArgumentOutOfRange("CallbackId");
9609 if (null == service) {
9610 throw ADP.ArgumentNull("Service");
9612 else if (UInt16.MaxValue < service.Length) {
9613 throw ADP.ArgumentOutOfRange("Service");
9616 if (-1 > timeout) {
9617 throw ADP.ArgumentOutOfRange("Timeout");
9620 // Header Length (uint) (included in size) (already written to output buffer)
9621 // Header Type (ushort)
9622 // NotifyID Length (ushort)
9623 // NotifyID UnicodeStream (unicode text)
9624 // SSBDeployment Length (ushort)
9625 // SSBDeployment UnicodeStream (unicode text)
9626 // Timeout (uint) -- optional
9627 // WEBDATA 102263: Don't send timeout value if it is 0
9629 int headerLength = 4 + 2 + 2 + (callbackId.Length * 2) + 2 + (service.Length * 2);
9630 if (timeout > 0)
9631 headerLength += 4;
9632 return headerLength;
9634 else {
9635 return 0;
9639 // Write query notificaiton header data, not including the notificaiton header length
9640 private void WriteQueryNotificationHeaderData(SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj) {
9641 Debug.Assert(_isYukon, "WriteQueryNotificationHeaderData called on a non-Yukon server");
9643 // We may need to update the notification header length if the header is changed in the future
9645 Debug.Assert (null != notificationRequest, "notificaitonRequest is null");
9647 string callbackId = notificationRequest.UserData;
9648 string service = notificationRequest.Options;
9649 int timeout = notificationRequest.Timeout;
9651 // we did verification in GetNotificationHeaderSize, so just assert here.
9652 Debug.Assert(null != callbackId, "CallbackId is null");
9653 Debug.Assert(UInt16.MaxValue >= callbackId.Length, "CallbackId length is out of range");
9654 Debug.Assert(null != service, "Service is null");
9655 Debug.Assert(UInt16.MaxValue >= service.Length, "Service length is out of range");
9656 Debug.Assert(-1 <= timeout, "Timeout");
9659 Bid.NotificationsTrace("<sc.TdsParser.WriteQueryNotificationHeader|DEP> NotificationRequest: userData: '%ls', options: '%ls', timeout: '%d'\n", notificationRequest.UserData, notificationRequest.Options, notificationRequest.Timeout);
9661 WriteShort(TdsEnums.HEADERTYPE_QNOTIFICATION, stateObj); // Query notifications Type
9663 WriteShort(callbackId.Length * 2, stateObj); // Length in bytes
9664 WriteString(callbackId, stateObj);
9666 WriteShort(service.Length * 2, stateObj); // Length in bytes
9667 WriteString(service, stateObj);
9668 if (timeout > 0)
9669 WriteInt(timeout, stateObj);
9672 // Write the trace header data, not including the trace header length
9673 private void WriteTraceHeaderData(TdsParserStateObject stateObj) {
9674 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");
9676 // We may need to update the trace header length if trace header is changed in the future
9678 ActivityCorrelator.ActivityId actId = ActivityCorrelator.Current;
9680 WriteShort(TdsEnums.HEADERTYPE_TRACE, stateObj); // Trace Header Type
9682 stateObj.WriteByteArray(actId.Id.ToByteArray(), GUID_SIZE, 0); // Id (Guid)
9683 WriteUnsignedInt(actId.Sequence, stateObj); // sequence number
9685 Bid.Trace("<sc.TdsParser.WriteTraceHeaderData|INFO> ActivityID %ls\n", actId.ToString());
9688 private void WriteRPCBatchHeaders(TdsParserStateObject stateObj, SqlNotificationRequest notificationRequest) {
9689 Debug.Assert(_isYukon, "WriteRPCBatchHeaders can only be called on Yukon or higher version servers");
9691 /* Header:
9692 TotalLength - DWORD - including all headers and lengths, including itself
9693 Each Data Session:
9695 HeaderLength - DWORD - including all header length fields, including itself
9696 HeaderType - USHORT
9697 HeaderData
9701 int notificationHeaderSize = GetNotificationHeaderSize(notificationRequest);
9703 const int marsHeaderSize = 18; // 4 + 2 + 8 + 4
9705 // Header Length (DWORD)
9706 // Header Type (ushort)
9707 // Trace Data Guid
9708 // Trace Data Sequence Number (uint)
9709 const int traceHeaderSize = 26; // 4 + 2 + GUID_SIZE + sizeof(UInt32);
9711 // TotalLength - DWORD - including all headers and lengths, including itself
9712 int totalHeaderLength = this.IncludeTraceHeader ? (4 + marsHeaderSize + notificationHeaderSize + traceHeaderSize) : (4 + marsHeaderSize + notificationHeaderSize);
9713 Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length");
9714 // Write total header length
9715 WriteInt(totalHeaderLength, stateObj);
9717 // Write Mars header length
9718 WriteInt(marsHeaderSize, stateObj);
9719 // Write Mars header data
9720 WriteMarsHeaderData(stateObj, CurrentTransaction);
9722 if (0 != notificationHeaderSize) {
9723 // Write Notification header length
9724 WriteInt(notificationHeaderSize, stateObj);
9725 // Write notificaiton header data
9726 WriteQueryNotificationHeaderData(notificationRequest, stateObj);
9729 if (IncludeTraceHeader) {
9731 // Write trace header length
9732 WriteInt(traceHeaderSize, stateObj);
9733 // Write trace header data
9734 WriteTraceHeaderData(stateObj);
9740 // Reverse function of GetTokenLength
9742 private void WriteTokenLength(byte token, int length, TdsParserStateObject stateObj) {
9743 int tokenLength = 0;
9745 Debug.Assert(token != 0, "0 length token!");
9747 // For Plp fields, this should only be used when writing to metadata header.
9748 // For actual data length, WriteDataLength should be used.
9749 // For Xml fields, there is no token length field. For MAX fields it is 0xffff.
9750 if (_isYukon) { // Handle Yukon specific exceptions
9751 if (TdsEnums.SQLUDT == token) {
9752 tokenLength = 8;
9754 else if (token == TdsEnums.SQLXMLTYPE) {
9755 tokenLength = 8;
9759 if (tokenLength == 0) {
9760 switch (token & TdsEnums.SQLLenMask) {
9761 case TdsEnums.SQLFixedLen:
9762 Debug.Assert(length == 0x01 << ((token & 0x0c) >> 2), "length does not match encoded length in token");
9763 tokenLength = 0;
9764 break;
9766 case TdsEnums.SQLZeroLen:
9767 tokenLength = 0;
9768 break;
9770 case TdsEnums.SQLVarLen:
9771 case TdsEnums.SQLVarCnt:
9772 if (0 != (token & 0x80))
9773 tokenLength = 2;
9774 else if (0 == (token & 0x0c))
9777 tokenLength = 4;
9778 else
9779 tokenLength = 1;
9781 break;
9783 default:
9784 Debug.Assert(false, "Unknown token length!");
9785 break;
9788 switch (tokenLength) {
9789 case 1:
9790 stateObj.WriteByte((byte)length);
9791 break;
9793 case 2:
9794 WriteShort(length, stateObj);
9795 break;
9797 case 4:
9798 WriteInt(length, stateObj);
9799 break;
9801 case 8:
9802 // In the metadata case we write 0xffff for partial length prefixed types.
9803 // For actual data length preceding data, WriteDataLength should be used.
9804 WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj);
9805 break;
9806 } // end switch
9810 // Returns true if BOM byte mark is needed for an XML value
9811 private bool IsBOMNeeded(MetaType type, object value) {
9812 if (type.NullableType == TdsEnums.SQLXMLTYPE) {
9813 Type currentType = value.GetType();
9815 if(currentType == typeof(SqlString)) {
9816 if (!((SqlString)value).IsNull && ((((SqlString)value).Value).Length > 0)) {
9817 if ((((SqlString)value).Value[0] & 0xff) != 0xff)
9818 return true;
9821 else if ((currentType == typeof(String)) && (((String)value).Length > 0)) {
9822 if ((value != null) && (((String)value)[0] & 0xff) != 0xff)
9823 return true;
9825 else if (currentType == typeof(SqlXml)) {
9826 if (!((SqlXml)value).IsNull)
9827 return true;
9829 else if (currentType == typeof(XmlDataFeed)) {
9830 return true; // Values will eventually converted to unicode string here
9833 return false;
9836 private Task GetTerminationTask(Task unterminatedWriteTask, object value, MetaType type, int actualLength, TdsParserStateObject stateObj, bool isDataFeed) {
9837 if (type.IsPlp && ((actualLength > 0) || isDataFeed)) {
9838 if (unterminatedWriteTask == null) {
9839 WriteInt(0, stateObj);
9840 return null;
9842 else {
9843 return AsyncHelper.CreateContinuationTask<int, TdsParserStateObject>(unterminatedWriteTask,
9844 WriteInt, 0, stateObj,
9845 connectionToDoom: _connHandler);
9848 else {
9849 return unterminatedWriteTask;
9854 private Task WriteSqlValue(object value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) {
9855 return GetTerminationTask(
9856 WriteUnterminatedSqlValue(value, type, actualLength, codePageByteSize, offset, stateObj),
9857 value, type, actualLength, stateObj, false);
9860 // For MAX types, this method can only write everything in one big chunk. If multiple
9861 // chunk writes needed, please use WritePlpBytes/WritePlpChars
9862 private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLength, int codePageByteSize, int offset, TdsParserStateObject stateObj) {
9863 Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) ||
9864 (value is INullable && !((INullable)value).IsNull)),
9865 "unexpected null SqlType!");
9867 // parameters are always sent over as BIG or N types
9868 switch (type.NullableType) {
9869 case TdsEnums.SQLFLTN:
9870 if (type.FixedLength == 4)
9871 WriteFloat(((SqlSingle)value).Value, stateObj);
9872 else {
9873 Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!");
9874 WriteDouble(((SqlDouble)value).Value, stateObj);
9877 break;
9879 case TdsEnums.SQLBIGBINARY:
9880 case TdsEnums.SQLBIGVARBINARY:
9881 case TdsEnums.SQLIMAGE:
9883 if (type.IsPlp) {
9884 WriteInt(actualLength, stateObj); // chunk length
9887 if (value is SqlBinary) {
9888 return stateObj.WriteByteArray(((SqlBinary)value).Value, actualLength, offset, canAccumulate:false);
9890 else {
9891 Debug.Assert(value is SqlBytes);
9892 return stateObj.WriteByteArray(((SqlBytes)value).Value, actualLength, offset, canAccumulate:false);
9896 case TdsEnums.SQLUNIQUEID:
9898 byte[] b = ((SqlGuid)value).ToByteArray();
9900 Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object");
9901 stateObj.WriteByteArray(b, actualLength, 0);
9902 break;
9905 case TdsEnums.SQLBITN:
9907 Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type");
9908 if (((SqlBoolean)value).Value == true)
9909 stateObj.WriteByte(1);
9910 else
9911 stateObj.WriteByte(0);
9913 break;
9916 case TdsEnums.SQLINTN:
9917 if (type.FixedLength == 1)
9918 stateObj.WriteByte(((SqlByte)value).Value);
9919 else
9920 if (type.FixedLength == 2)
9921 WriteShort(((SqlInt16)value).Value, stateObj);
9922 else
9923 if (type.FixedLength == 4)
9924 WriteInt(((SqlInt32)value).Value, stateObj);
9925 else {
9926 Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture));
9927 WriteLong(((SqlInt64)value).Value, stateObj);
9930 break;
9932 case TdsEnums.SQLBIGCHAR:
9933 case TdsEnums.SQLBIGVARCHAR:
9934 case TdsEnums.SQLTEXT:
9935 if (type.IsPlp) {
9936 WriteInt(codePageByteSize, stateObj); // chunk length
9938 if (value is SqlChars) {
9939 String sch = new String(((SqlChars)value).Value);
9941 return WriteEncodingChar(sch, actualLength, offset, _defaultEncoding, stateObj, canAccumulate:false);
9943 else {
9944 Debug.Assert(value is SqlString);
9945 return WriteEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate:false);
9949 case TdsEnums.SQLNCHAR:
9950 case TdsEnums.SQLNVARCHAR:
9951 case TdsEnums.SQLNTEXT:
9952 case TdsEnums.SQLXMLTYPE:
9954 if (type.IsPlp) {
9955 if(IsBOMNeeded(type, value)) {
9956 WriteInt(actualLength+2, stateObj); // chunk length
9957 WriteShort(TdsEnums.XMLUNICODEBOM , stateObj);
9958 } else {
9959 WriteInt(actualLength, stateObj); // chunk length
9963 // convert to cchars instead of cbytes
9964 // Xml type is already converted to string through GetCoercedValue
9965 if (actualLength != 0)
9966 actualLength >>= 1;
9968 if (value is SqlChars) {
9969 return WriteCharArray(((SqlChars)value).Value, actualLength, offset, stateObj, canAccumulate:false);
9971 else {
9972 Debug.Assert(value is SqlString);
9973 return WriteString(((SqlString)value).Value, actualLength, offset, stateObj, canAccumulate:false);
9976 case TdsEnums.SQLNUMERICN:
9977 Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes");
9978 WriteSqlDecimal((SqlDecimal)value, stateObj);
9979 break;
9981 case TdsEnums.SQLDATETIMN:
9982 SqlDateTime dt = (SqlDateTime)value;
9984 if (type.FixedLength == 4) {
9985 if (0 > dt.DayTicks || dt.DayTicks > UInt16.MaxValue)
9986 throw SQL.SmallDateTimeOverflow(dt.ToString());
9988 WriteShort(dt.DayTicks, stateObj);
9989 WriteShort(dt.TimeTicks / SqlDateTime.SQLTicksPerMinute, stateObj);
9991 else {
9992 WriteInt(dt.DayTicks, stateObj);
9993 WriteInt(dt.TimeTicks, stateObj);
9996 break;
9998 case TdsEnums.SQLMONEYN:
10000 WriteSqlMoney((SqlMoney)value, type.FixedLength, stateObj);
10001 break;
10004 case TdsEnums.SQLUDT:
10005 Debug.Assert(false, "Called WriteSqlValue on UDT param.Should have already been handled");
10006 throw SQL.UDTUnexpectedResult(value.GetType().AssemblyQualifiedName);
10008 default:
10009 Debug.Assert(false, "Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null));
10010 break;
10011 } // switch
10012 // return point for accumualated writes, note: non-accumulated writes returned from their case statements
10013 return null;
10016 private class TdsOutputStream : Stream {
10017 TdsParser _parser;
10018 TdsParserStateObject _stateObj;
10019 byte[] _preambleToStrip;
10021 public TdsOutputStream(TdsParser parser, TdsParserStateObject stateObj, byte[] preambleToStrip) {
10022 _parser = parser;
10023 _stateObj = stateObj;
10024 _preambleToStrip = preambleToStrip;
10027 public override bool CanRead {
10028 get { return false; }
10031 public override bool CanSeek {
10032 get { return false; }
10035 public override bool CanWrite {
10036 get { return true; }
10039 public override void Flush() {
10040 // NOOP
10043 public override long Length {
10044 get { throw new NotSupportedException(); }
10047 public override long Position {
10048 get {
10049 throw new NotSupportedException();
10051 set {
10052 throw new NotSupportedException();
10056 public override int Read(byte[] buffer, int offset, int count) {
10057 throw new NotSupportedException();
10060 public override long Seek(long offset, SeekOrigin origin) {
10061 throw new NotSupportedException();
10064 public override void SetLength(long value) {
10065 throw new NotSupportedException();
10068 private void StripPreamble(byte[] buffer, ref int offset, ref int count) {
10069 if (_preambleToStrip != null && count >= _preambleToStrip.Length) {
10071 for (int idx = 0; idx < _preambleToStrip.Length; idx++) {
10072 if (_preambleToStrip[idx] != buffer[idx]) {
10073 _preambleToStrip = null;
10074 return;
10078 offset += _preambleToStrip.Length;
10079 count -= _preambleToStrip.Length;
10081 _preambleToStrip = null;
10084 public override void Write(byte[] buffer, int offset, int count) {
10085 Debug.Assert(!_parser._asyncWrite);
10086 ValidateWriteParameters(buffer, offset, count);
10088 StripPreamble(buffer, ref offset, ref count);
10090 if (count > 0) {
10091 _parser.WriteInt(count, _stateObj); // write length of chunk
10092 _stateObj.WriteByteArray(buffer, count, offset);
10096 public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) {
10097 Debug.Assert(_parser._asyncWrite);
10098 ValidateWriteParameters(buffer, offset, count);
10100 StripPreamble(buffer, ref offset, ref count);
10102 RuntimeHelpers.PrepareConstrainedRegions();
10103 try {
10104 #if DEBUG
10105 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
10107 RuntimeHelpers.PrepareConstrainedRegions();
10108 try {
10109 tdsReliabilitySection.Start();
10110 #else
10112 #endif //DEBUG
10113 Task task = null;
10114 if (count > 0) {
10115 _parser.WriteInt(count, _stateObj); // write length of chunk
10116 task = _stateObj.WriteByteArray(buffer, count, offset, canAccumulate: false);
10118 if (task == null) {
10119 return CompletedTask;
10121 else {
10122 return task;
10125 #if DEBUG
10126 finally {
10127 tdsReliabilitySection.Stop();
10129 #endif //DEBUG
10131 catch (System.OutOfMemoryException) {
10132 _parser._connHandler.DoomThisConnection();
10133 throw;
10135 catch (System.StackOverflowException) {
10136 _parser._connHandler.DoomThisConnection();
10137 throw;
10139 catch (System.Threading.ThreadAbortException) {
10140 _parser._connHandler.DoomThisConnection();
10141 throw;
10145 internal static void ValidateWriteParameters(byte[] buffer, int offset, int count) {
10146 if (buffer == null) {
10147 throw ADP.ArgumentNull(ADP.ParameterBuffer);
10149 if (offset < 0) {
10150 throw ADP.ArgumentOutOfRange(ADP.ParameterOffset);
10152 if (count < 0) {
10153 throw ADP.ArgumentOutOfRange(ADP.ParameterCount);
10155 try {
10156 if (checked(offset + count) > buffer.Length) {
10157 throw ExceptionBuilder.InvalidOffsetLength();
10160 catch (OverflowException) {
10161 // If we've overflowed when adding offset and count, then they never would have fit into buffer anyway
10162 throw ExceptionBuilder.InvalidOffsetLength();
10168 private class ConstrainedTextWriter : TextWriter {
10169 TextWriter _next;
10170 int _size;
10171 int _written;
10173 public ConstrainedTextWriter(TextWriter next, int size) {
10174 _next = next;
10175 _size = size;
10176 _written = 0;
10178 if (_size < 1) {
10179 _size = int.MaxValue;
10183 public bool IsComplete {
10184 get {
10185 return _size > 0 && _written >= _size;
10189 public override Encoding Encoding {
10190 get { return _next.Encoding; }
10193 public override void Flush() {
10194 _next.Flush();
10197 public override Task FlushAsync() {
10198 return _next.FlushAsync();
10201 public override void Write(char value) {
10202 if (_written < _size) {
10203 _next.Write(value);
10204 _written++;
10206 Debug.Assert(_size < 0 || _written <= _size, string.Format("Length of data written exceeds specified length. Written: {0}, specified: {1}", _written, _size));
10209 public override void Write(char[] buffer, int index, int count) {
10211 ValidateWriteParameters(buffer, index, count);
10213 Debug.Assert(_size >= _written);
10214 count = Math.Min(_size - _written, count);
10215 if (count > 0) {
10216 _next.Write(buffer, index, count);
10218 _written += count;
10221 public override Task WriteAsync(char value) {
10223 if (_written < _size) {
10224 _written++;
10225 return _next.WriteAsync(value);
10228 return CompletedTask;
10231 public override Task WriteAsync(char[] buffer, int index, int count) {
10233 ValidateWriteParameters(buffer, index, count);
10235 Debug.Assert(_size >= _written);
10236 count = Math.Min(_size - _written, count);
10237 if (count > 0) {
10238 _written += count;
10239 return _next.WriteAsync(buffer, index, count);
10242 return CompletedTask;
10245 public override Task WriteAsync(string value) {
10246 return WriteAsync(value.ToCharArray());
10249 internal static void ValidateWriteParameters(char[] buffer, int offset, int count) {
10250 if (buffer == null) {
10251 throw ADP.ArgumentNull(ADP.ParameterBuffer);
10253 if (offset < 0) {
10254 throw ADP.ArgumentOutOfRange(ADP.ParameterOffset);
10256 if (count < 0) {
10257 throw ADP.ArgumentOutOfRange(ADP.ParameterCount);
10259 try {
10260 if (checked(offset + count) > buffer.Length) {
10261 throw ExceptionBuilder.InvalidOffsetLength();
10264 catch (OverflowException) {
10265 // If we've overflowed when adding offset and count, then they never would have fit into buffer anyway
10266 throw ExceptionBuilder.InvalidOffsetLength();
10272 private async Task WriteXmlFeed(XmlDataFeed feed, TdsParserStateObject stateObj, bool needBom, Encoding encoding, int size) {
10273 byte[] preambleToSkip = null;
10274 if (!needBom) {
10275 preambleToSkip = encoding.GetPreamble();
10277 ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, preambleToSkip), encoding), size);
10279 XmlWriterSettings writerSettings = new XmlWriterSettings();
10280 writerSettings.CloseOutput = false; // don't close the memory stream
10281 writerSettings.ConformanceLevel = ConformanceLevel.Fragment;
10282 if (_asyncWrite) {
10283 writerSettings.Async = true;
10285 XmlWriter ww = XmlWriter.Create(writer, writerSettings);
10287 if (feed._source.ReadState == ReadState.Initial) {
10288 feed._source.Read();
10291 while (!feed._source.EOF && !writer.IsComplete) {
10293 // We are copying nodes from a reader to a writer. This will cause the
10294 // XmlDeclaration to be emitted despite ConformanceLevel.Fragment above.
10295 // Therefore, we filter out the XmlDeclaration while copying.
10296 if (feed._source.NodeType == XmlNodeType.XmlDeclaration) {
10297 feed._source.Read();
10298 continue;
10301 if (_asyncWrite) {
10302 await ww.WriteNodeAsync(feed._source, true).ConfigureAwait(false);
10304 else {
10305 ww.WriteNode(feed._source, true);
10309 if (_asyncWrite) {
10310 await ww.FlushAsync().ConfigureAwait(false);
10312 else {
10313 ww.Flush();
10317 private async Task WriteTextFeed(TextDataFeed feed, Encoding encoding, bool needBom, TdsParserStateObject stateObj, int size) {
10318 Debug.Assert(encoding == null || !needBom);
10319 char[] inBuff = new char[constTextBufferSize];
10321 encoding = encoding ?? new UnicodeEncoding(false, false);
10322 ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null), encoding), size);
10324 if (needBom) {
10325 if (_asyncWrite) {
10326 await writer.WriteAsync((char)TdsEnums.XMLUNICODEBOM).ConfigureAwait(false);
10328 else {
10329 writer.Write((char)TdsEnums.XMLUNICODEBOM);
10333 int nWritten = 0;
10334 do {
10335 int nRead = 0;
10337 if (_asyncWrite) {
10338 nRead = await feed._source.ReadBlockAsync(inBuff, 0, constTextBufferSize).ConfigureAwait(false);
10340 else {
10341 nRead = feed._source.ReadBlock(inBuff, 0, constTextBufferSize);
10344 if (nRead == 0) {
10345 break;
10348 if (_asyncWrite) {
10349 await writer.WriteAsync(inBuff, 0, nRead).ConfigureAwait(false);
10351 else {
10352 writer.Write(inBuff, 0, nRead);
10355 nWritten += nRead;
10356 } while (!writer.IsComplete);
10358 if (_asyncWrite) {
10359 await writer.FlushAsync().ConfigureAwait(false);
10361 else {
10362 writer.Flush();
10366 private async Task WriteStreamFeed(StreamDataFeed feed, TdsParserStateObject stateObj, int len) {
10367 TdsOutputStream output = new TdsOutputStream(this, stateObj, null);
10368 byte[] buff = new byte[constBinBufferSize];
10369 int nWritten = 0;
10370 do {
10371 int nRead = 0;
10372 int readSize = constBinBufferSize;
10373 if (len > 0 && nWritten + readSize > len) {
10374 readSize = len - nWritten;
10377 Debug.Assert(readSize >= 0);
10379 if (_asyncWrite) {
10380 nRead = await feed._source.ReadAsync(buff, 0, readSize).ConfigureAwait(false);
10382 else {
10383 nRead = feed._source.Read(buff, 0, readSize);
10386 if (nRead == 0) {
10387 return;
10390 if (_asyncWrite) {
10391 await output.WriteAsync(buff, 0, nRead).ConfigureAwait(false);
10393 else {
10394 output.Write(buff, 0, nRead);
10397 nWritten += nRead;
10398 } while (len <= 0 || nWritten < len);
10401 private Task NullIfCompletedWriteTask(Task task) {
10402 if (task == null) {
10403 return null;
10405 switch (task.Status) {
10406 case TaskStatus.RanToCompletion:
10407 return null;
10408 case TaskStatus.Faulted:
10409 throw task.Exception.InnerException;
10410 case TaskStatus.Canceled:
10411 throw SQL.OperationCancelled();
10412 default:
10413 return task;
10417 private Task WriteValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) {
10418 return GetTerminationTask(WriteUnterminatedValue(value, type, scale, actualLength, encodingByteSize, offset, stateObj, paramSize, isDataFeed),
10419 value, type, actualLength, stateObj, isDataFeed);
10422 // For MAX types, this method can only write everything in one big chunk. If multiple
10423 // chunk writes needed, please use WritePlpBytes/WritePlpChars
10424 private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed)
10426 Debug.Assert((null != value) && (DBNull.Value != value), "unexpected missing or empty object");
10428 // parameters are always sent over as BIG or N types
10429 switch (type.NullableType) {
10430 case TdsEnums.SQLFLTN:
10431 if (type.FixedLength == 4)
10432 WriteFloat((Single)value, stateObj);
10433 else {
10434 Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!");
10435 WriteDouble((Double)value, stateObj);
10438 break;
10440 case TdsEnums.SQLBIGBINARY:
10441 case TdsEnums.SQLBIGVARBINARY:
10442 case TdsEnums.SQLIMAGE:
10443 case TdsEnums.SQLUDT: {
10444 // An array should be in the object
10445 Debug.Assert(isDataFeed || value is byte[], "Value should be an array of bytes");
10446 Debug.Assert(!isDataFeed || value is StreamDataFeed, "Value should be a stream");
10448 if (isDataFeed) {
10449 Debug.Assert(type.IsPlp,"Stream assigned to non-PLP was not converted!");
10450 return NullIfCompletedWriteTask(WriteStreamFeed((StreamDataFeed)value, stateObj, paramSize));
10452 else {
10453 if (type.IsPlp) {
10454 WriteInt(actualLength, stateObj); // chunk length
10457 return stateObj.WriteByteArray((byte[])value, actualLength, offset, canAccumulate: false);
10461 case TdsEnums.SQLUNIQUEID: {
10462 System.Guid guid = (System.Guid)value;
10463 byte[] b = guid.ToByteArray();
10465 Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object");
10466 stateObj.WriteByteArray(b, actualLength, 0);
10467 break;
10470 case TdsEnums.SQLBITN: {
10471 Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type");
10472 if ((bool)value == true)
10473 stateObj.WriteByte(1);
10474 else
10475 stateObj.WriteByte(0);
10477 break;
10480 case TdsEnums.SQLINTN:
10481 if (type.FixedLength == 1)
10482 stateObj.WriteByte((byte)value);
10483 else if (type.FixedLength == 2)
10484 WriteShort((Int16)value, stateObj);
10485 else if (type.FixedLength == 4)
10486 WriteInt((Int32)value, stateObj);
10487 else {
10488 Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture));
10489 WriteLong((Int64)value, stateObj);
10492 break;
10494 case TdsEnums.SQLBIGCHAR:
10495 case TdsEnums.SQLBIGVARCHAR:
10496 case TdsEnums.SQLTEXT: {
10497 Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader");
10498 Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string");
10500 if (isDataFeed) {
10501 Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!");
10502 TextDataFeed tdf = value as TextDataFeed;
10503 if (tdf == null) {
10504 return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, needBom:true, encoding:_defaultEncoding, size:paramSize));
10506 else {
10507 return NullIfCompletedWriteTask(WriteTextFeed(tdf, _defaultEncoding, false, stateObj, paramSize));
10510 else {
10511 if (type.IsPlp) {
10512 WriteInt(encodingByteSize, stateObj); // chunk length
10514 if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value
10515 return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false);
10517 else {
10518 return WriteEncodingChar((string)value, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false);
10522 case TdsEnums.SQLNCHAR:
10523 case TdsEnums.SQLNVARCHAR:
10524 case TdsEnums.SQLNTEXT:
10525 case TdsEnums.SQLXMLTYPE: {
10526 Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader");
10527 Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string");
10529 if (isDataFeed) {
10530 Debug.Assert(type.IsPlp, "Stream assigned to non-PLP was not converted!");
10531 TextDataFeed tdf = value as TextDataFeed;
10532 if (tdf == null) {
10533 return NullIfCompletedWriteTask(WriteXmlFeed((XmlDataFeed)value, stateObj, IsBOMNeeded(type, value), Encoding.Unicode, paramSize));
10535 else {
10536 return NullIfCompletedWriteTask(WriteTextFeed(tdf, null, IsBOMNeeded(type, value), stateObj, paramSize));
10539 else {
10540 if (type.IsPlp) {
10541 if (IsBOMNeeded(type, value)) {
10542 WriteInt(actualLength + 2, stateObj); // chunk length
10543 WriteShort(TdsEnums.XMLUNICODEBOM, stateObj);
10545 else {
10546 WriteInt(actualLength, stateObj); // chunk length
10549 if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value
10550 return stateObj.WriteByteArray((byte[])value, actualLength, 0, canAccumulate: false);
10552 else {
10553 // convert to cchars instead of cbytes
10554 actualLength >>= 1;
10555 return WriteString((string)value, actualLength, offset, stateObj, canAccumulate: false);
10559 case TdsEnums.SQLNUMERICN:
10560 Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes");
10561 WriteDecimal((Decimal)value, stateObj);
10562 break;
10564 case TdsEnums.SQLDATETIMN:
10565 Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length");
10567 TdsDateTime dt = MetaType.FromDateTime((DateTime)value, (byte)type.FixedLength);
10569 if (type.FixedLength == 4) {
10570 if (0 > dt.days || dt.days > UInt16.MaxValue)
10571 throw SQL.SmallDateTimeOverflow(MetaType.ToDateTime(dt.days, dt.time, 4).ToString(CultureInfo.InvariantCulture));
10573 WriteShort(dt.days, stateObj);
10574 WriteShort(dt.time, stateObj);
10576 else {
10577 WriteInt(dt.days, stateObj);
10578 WriteInt(dt.time, stateObj);
10581 break;
10583 case TdsEnums.SQLMONEYN: {
10584 WriteCurrency((Decimal)value, type.FixedLength, stateObj);
10585 break;
10588 case TdsEnums.SQLDATE: {
10589 WriteDate((DateTime)value, stateObj);
10590 break;
10593 case TdsEnums.SQLTIME:
10594 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10595 throw SQL.TimeScaleValueOutOfRange(scale);
10597 WriteTime((TimeSpan)value, scale, actualLength, stateObj);
10598 break;
10600 case TdsEnums.SQLDATETIME2:
10601 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10602 throw SQL.TimeScaleValueOutOfRange(scale);
10604 WriteDateTime2((DateTime)value, scale, actualLength, stateObj);
10605 break;
10607 case TdsEnums.SQLDATETIMEOFFSET:
10608 WriteDateTimeOffset((DateTimeOffset)value, scale, actualLength, stateObj);
10609 break;
10611 default:
10612 Debug.Assert(false, "Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null));
10613 break;
10614 } // switch
10615 // return point for accumualated writes, note: non-accumulated writes returned from their case statements
10616 return null;
10617 // Debug.WriteLine("value: " + value.ToString(CultureInfo.InvariantCulture));
10620 /// <summary>
10621 /// Write parameter encryption metadata and returns a task if necessary.
10622 /// </summary>
10623 private Task WriteEncryptionMetadata(Task terminatedWriteTask, SqlColumnEncryptionInputParameterInfo columnEncryptionParameterInfo, TdsParserStateObject stateObj) {
10624 Debug.Assert(columnEncryptionParameterInfo != null, @"columnEncryptionParameterInfo cannot be null");
10625 Debug.Assert(stateObj != null, @"stateObj cannot be null");
10627 // If there is not task already, simply write the encryption metadata synchronously.
10628 if (terminatedWriteTask == null) {
10629 WriteEncryptionMetadata(columnEncryptionParameterInfo, stateObj);
10630 return null;
10632 else {
10633 // Otherwise, create a continuation task to write the encryption metadata after the previous write completes.
10634 return AsyncHelper.CreateContinuationTask<SqlColumnEncryptionInputParameterInfo, TdsParserStateObject>(terminatedWriteTask,
10635 WriteEncryptionMetadata, columnEncryptionParameterInfo, stateObj,
10636 connectionToDoom: _connHandler);
10640 /// <summary>
10641 /// Write parameter encryption metadata.
10642 /// </summary>
10643 private void WriteEncryptionMetadata(SqlColumnEncryptionInputParameterInfo columnEncryptionParameterInfo, TdsParserStateObject stateObj) {
10644 Debug.Assert(columnEncryptionParameterInfo != null, @"columnEncryptionParameterInfo cannot be null");
10645 Debug.Assert(stateObj != null, @"stateObj cannot be null");
10647 // Write the TypeInfo.
10648 WriteSmiTypeInfo(columnEncryptionParameterInfo.ParameterMetadata, stateObj);
10650 // Write the serialized array in columnEncryptionParameterInfo.
10651 stateObj.WriteByteArray(columnEncryptionParameterInfo.SerializedWireFormat,
10652 columnEncryptionParameterInfo.SerializedWireFormat.Length,
10653 offsetBuffer: 0);
10656 // For MAX types, this method can only write everything in one big chunk. If multiple
10657 // chunk writes needed, please use WritePlpBytes/WritePlpChars
10658 private byte[] SerializeUnencryptedValue(object value, MetaType type, byte scale, int actualLength, int offset, bool isDataFeed, byte normalizationVersion, TdsParserStateObject stateObj) {
10659 Debug.Assert((null != value) && (DBNull.Value != value), "unexpected missing or empty object");
10661 if (normalizationVersion != 0x01) {
10662 throw SQL.UnsupportedNormalizationVersion(normalizationVersion);
10665 // parameters are always sent over as BIG or N types
10666 switch (type.NullableType) {
10667 case TdsEnums.SQLFLTN:
10668 if (type.FixedLength == 4)
10669 return SerializeFloat((Single)value);
10670 else {
10671 Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!");
10672 return SerializeDouble((Double)value);
10675 case TdsEnums.SQLBIGBINARY:
10676 case TdsEnums.SQLBIGVARBINARY:
10677 case TdsEnums.SQLIMAGE:
10678 case TdsEnums.SQLUDT: {
10679 Debug.Assert(!isDataFeed, "We cannot seriliaze streams");
10680 Debug.Assert(value is byte[], "Value should be an array of bytes");
10682 byte[] b = new byte[actualLength];
10683 Buffer.BlockCopy((byte[])value, offset, b, 0, actualLength);
10684 return b;
10687 case TdsEnums.SQLUNIQUEID: {
10688 System.Guid guid = (System.Guid)value;
10689 byte[] b = guid.ToByteArray();
10691 Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object");
10692 return b;
10695 case TdsEnums.SQLBITN: {
10696 Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type");
10698 // We normalize to allow conversion across data types. BIT is serialized into a BIGINT.
10699 return SerializeLong((bool)value == true ? 1 : 0, stateObj);
10702 case TdsEnums.SQLINTN:
10703 if (type.FixedLength == 1)
10704 return SerializeLong((byte)value, stateObj);
10706 if (type.FixedLength == 2)
10707 return SerializeLong((Int16)value, stateObj);
10709 if (type.FixedLength == 4)
10710 return SerializeLong((Int32)value, stateObj);
10712 Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture));
10713 return SerializeLong((Int64)value, stateObj);
10715 case TdsEnums.SQLBIGCHAR:
10716 case TdsEnums.SQLBIGVARCHAR:
10717 case TdsEnums.SQLTEXT: {
10718 Debug.Assert(!isDataFeed, "We cannot seriliaze streams");
10719 Debug.Assert((value is string || value is byte[]), "Value is a byte array or string");
10721 if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value
10722 byte[] b = new byte[actualLength];
10723 Buffer.BlockCopy((byte[])value, 0, b, 0, actualLength);
10724 return b;
10726 else {
10727 return SerializeEncodingChar((string)value, actualLength, offset, _defaultEncoding);
10730 case TdsEnums.SQLNCHAR:
10731 case TdsEnums.SQLNVARCHAR:
10732 case TdsEnums.SQLNTEXT:
10733 case TdsEnums.SQLXMLTYPE: {
10734 Debug.Assert(!isDataFeed, "We cannot seriliaze streams");
10735 Debug.Assert((value is string || value is byte[]), "Value is a byte array or string");
10737 if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value
10738 byte[] b = new byte[actualLength];
10739 Buffer.BlockCopy((byte[])value, 0, b, 0, actualLength);
10740 return b;
10742 else { // convert to cchars instead of cbytes
10743 actualLength >>= 1;
10744 return SerializeString((string)value, actualLength, offset);
10747 case TdsEnums.SQLNUMERICN:
10748 Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes");
10749 return SerializeDecimal((Decimal)value, stateObj);
10751 case TdsEnums.SQLDATETIMN:
10752 Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length");
10754 TdsDateTime dt = MetaType.FromDateTime((DateTime)value, (byte)type.FixedLength);
10756 if (type.FixedLength == 4) {
10757 if (0 > dt.days || dt.days > UInt16.MaxValue)
10758 throw SQL.SmallDateTimeOverflow(MetaType.ToDateTime(dt.days, dt.time, 4).ToString(CultureInfo.InvariantCulture));
10760 if (null == stateObj._bIntBytes) {
10761 stateObj._bIntBytes = new byte[4];
10764 byte[] b = stateObj._bIntBytes;
10765 int current = 0;
10767 byte[] bPart = SerializeShort(dt.days, stateObj);
10768 Buffer.BlockCopy(bPart, 0, b, current, 2);
10769 current += 2;
10771 bPart = SerializeShort(dt.time, stateObj);
10772 Buffer.BlockCopy(bPart, 0, b, current, 2);
10774 return b;
10776 else {
10777 if (null == stateObj._bLongBytes) {
10778 stateObj._bLongBytes = new byte[8];
10780 byte[] b = stateObj._bLongBytes;
10781 int current = 0;
10783 byte[] bPart = SerializeInt(dt.days, stateObj);
10784 Buffer.BlockCopy(bPart, 0, b, current, 4);
10785 current += 4;
10787 bPart = SerializeInt(dt.time, stateObj);
10788 Buffer.BlockCopy(bPart, 0, b, current, 4);
10790 return b;
10793 case TdsEnums.SQLMONEYN: {
10794 return SerializeCurrency((Decimal)value, type.FixedLength, stateObj);
10797 case TdsEnums.SQLDATE: {
10798 return SerializeDate((DateTime)value);
10801 case TdsEnums.SQLTIME:
10802 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10803 throw SQL.TimeScaleValueOutOfRange(scale);
10805 return SerializeTime((TimeSpan)value, scale, actualLength);
10807 case TdsEnums.SQLDATETIME2:
10808 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10809 throw SQL.TimeScaleValueOutOfRange(scale);
10811 return SerializeDateTime2((DateTime)value, scale, actualLength);
10813 case TdsEnums.SQLDATETIMEOFFSET:
10814 if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) {
10815 throw SQL.TimeScaleValueOutOfRange(scale);
10817 return SerializeDateTimeOffset((DateTimeOffset)value, scale, actualLength);
10819 default:
10820 throw SQL.UnsupportedDatatypeEncryption(type.TypeName);
10821 } // switch
10822 // Debug.WriteLine("value: " + value.ToString(CultureInfo.InvariantCulture));
10825 // For MAX types, this method can only write everything in one big chunk. If multiple
10826 // chunk writes needed, please use WritePlpBytes/WritePlpChars
10827 private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int actualLength, int offset, byte normalizationVersion, TdsParserStateObject stateObj) {
10828 Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) ||
10829 (value is INullable && !((INullable)value).IsNull)),
10830 "unexpected null SqlType!");
10832 if (normalizationVersion != 0x01) {
10833 throw SQL.UnsupportedNormalizationVersion(normalizationVersion);
10836 // parameters are always sent over as BIG or N types
10837 switch (type.NullableType) {
10838 case TdsEnums.SQLFLTN:
10839 if (type.FixedLength == 4)
10840 return SerializeFloat(((SqlSingle)value).Value);
10841 else {
10842 Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!");
10843 return SerializeDouble(((SqlDouble)value).Value);
10846 case TdsEnums.SQLBIGBINARY:
10847 case TdsEnums.SQLBIGVARBINARY:
10848 case TdsEnums.SQLIMAGE: {
10849 byte[] b = new byte[actualLength];
10851 if (value is SqlBinary) {
10852 Buffer.BlockCopy(((SqlBinary)value).Value, offset, b, 0, actualLength);
10854 else {
10855 Debug.Assert(value is SqlBytes);
10856 Buffer.BlockCopy(((SqlBytes)value).Value, offset, b, 0, actualLength);
10858 return b;
10861 case TdsEnums.SQLUNIQUEID: {
10862 byte[] b = ((SqlGuid)value).ToByteArray();
10864 Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object");
10865 return b;
10868 case TdsEnums.SQLBITN: {
10869 Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type");
10871 // We normalize to allow conversion across data types. BIT is serialized into a BIGINT.
10872 return SerializeLong(((SqlBoolean)value).Value == true ? 1 : 0, stateObj);
10875 case TdsEnums.SQLINTN:
10876 // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT.
10877 if (type.FixedLength == 1)
10878 return SerializeLong(((SqlByte)value).Value, stateObj);
10880 if (type.FixedLength == 2)
10881 return SerializeLong(((SqlInt16)value).Value, stateObj);
10883 if (type.FixedLength == 4)
10884 return SerializeLong(((SqlInt32)value).Value, stateObj);
10885 else {
10886 Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture));
10887 return SerializeLong(((SqlInt64)value).Value, stateObj);
10890 case TdsEnums.SQLBIGCHAR:
10891 case TdsEnums.SQLBIGVARCHAR:
10892 case TdsEnums.SQLTEXT:
10893 if (value is SqlChars) {
10894 String sch = new String(((SqlChars)value).Value);
10895 return SerializeEncodingChar(sch, actualLength, offset, _defaultEncoding);
10897 else {
10898 Debug.Assert(value is SqlString);
10899 return SerializeEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding);
10903 case TdsEnums.SQLNCHAR:
10904 case TdsEnums.SQLNVARCHAR:
10905 case TdsEnums.SQLNTEXT:
10906 case TdsEnums.SQLXMLTYPE:
10907 // convert to cchars instead of cbytes
10908 // Xml type is already converted to string through GetCoercedValue
10909 if (actualLength != 0)
10910 actualLength >>= 1;
10912 if (value is SqlChars) {
10913 return SerializeCharArray(((SqlChars)value).Value, actualLength, offset);
10915 else {
10916 Debug.Assert(value is SqlString);
10917 return SerializeString(((SqlString)value).Value, actualLength, offset);
10920 case TdsEnums.SQLNUMERICN:
10921 Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes");
10922 return SerializeSqlDecimal((SqlDecimal)value, stateObj);
10924 case TdsEnums.SQLDATETIMN:
10925 SqlDateTime dt = (SqlDateTime)value;
10927 if (type.FixedLength == 4) {
10928 if (0 > dt.DayTicks || dt.DayTicks > UInt16.MaxValue)
10929 throw SQL.SmallDateTimeOverflow(dt.ToString());
10931 if (null == stateObj._bIntBytes) {
10932 stateObj._bIntBytes = new byte[4];
10935 byte[] b = stateObj._bIntBytes;
10936 int current = 0;
10938 byte[] bPart = SerializeShort(dt.DayTicks, stateObj);
10939 Buffer.BlockCopy(bPart, 0, b, current, 2);
10940 current += 2;
10942 bPart = SerializeShort(dt.TimeTicks / SqlDateTime.SQLTicksPerMinute, stateObj);
10943 Buffer.BlockCopy(bPart, 0, b, current, 2);
10945 return b;
10947 else {
10948 if (null == stateObj._bLongBytes) {
10949 stateObj._bLongBytes = new byte[8];
10952 byte[] b = stateObj._bLongBytes;
10953 int current = 0;
10955 byte[] bPart = SerializeInt(dt.DayTicks, stateObj);
10956 Buffer.BlockCopy(bPart, 0, b, current, 4);
10957 current += 4;
10959 bPart = SerializeInt(dt.TimeTicks, stateObj);
10960 Buffer.BlockCopy(bPart, 0, b, current, 4);
10962 return b;
10965 case TdsEnums.SQLMONEYN: {
10966 return SerializeSqlMoney((SqlMoney)value, type.FixedLength, stateObj);
10969 default:
10970 throw SQL.UnsupportedDatatypeEncryption(type.TypeName);
10971 } // switch
10975 // we always send over nullable types for parameters so we always write the varlen fields
10978 internal void WriteParameterVarLen(MetaType type, int size, bool isNull, TdsParserStateObject stateObj, bool unknownLength=false) {
10979 if (type.IsLong) { // text/image/SQLVariant have a 4 byte length, plp datatypes have 8 byte lengths
10980 if (isNull) {
10981 if (type.IsPlp) {
10982 WriteLong(unchecked((long)TdsEnums.SQL_PLP_NULL), stateObj);
10984 else {
10985 WriteInt(unchecked((int)TdsEnums.VARLONGNULL), stateObj);
10988 else if (type.NullableType == TdsEnums.SQLXMLTYPE || unknownLength) {
10989 WriteUnsignedLong(TdsEnums.SQL_PLP_UNKNOWNLEN, stateObj);
10991 else if (type.IsPlp) {
10992 // Non-xml plp types
10993 WriteLong((long)size, stateObj);
10995 else {
10996 WriteInt(size, stateObj);
10999 else if (type.IsVarTime) {
11000 if (isNull) {
11001 stateObj.WriteByte(TdsEnums.FIXEDNULL);
11003 else {
11004 stateObj.WriteByte((byte)size);
11007 else if (false == type.IsFixed) { // non-long but variable length column, must be a BIG* type: 2 byte length
11008 if (isNull) {
11009 WriteShort(TdsEnums.VARNULL, stateObj);
11011 else {
11012 WriteShort(size, stateObj);
11015 else {
11016 if (isNull) {
11017 stateObj.WriteByte(TdsEnums.FIXEDNULL);
11019 else {
11020 Debug.Assert(type.FixedLength <= 0xff, "WriteParameterVarLen: invalid one byte length!");
11021 stateObj.WriteByte((byte)(type.FixedLength & 0xff)); // 1 byte for everything else
11026 // Reads the next chunk in a nvarchar(max) data stream.
11027 // This call must be preceeded by a call to ReadPlpLength or ReadDataLength.
11028 // Will not start reading into the next chunk if bytes requested is larger than
11029 // the current chunk length. Do another ReadPlpLength, ReadPlpUnicodeChars in that case.
11030 // Returns the actual chars read
11031 private bool TryReadPlpUnicodeCharsChunk(char[] buff, int offst, int len, TdsParserStateObject stateObj, out int charsRead) {
11033 Debug.Assert((buff == null && len == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!");
11034 Debug.Assert((stateObj._longlen != 0) && (stateObj._longlen != TdsEnums.SQL_PLP_NULL),
11035 "Out of sync plp read request");
11036 if (stateObj._longlenleft == 0) {
11037 Debug.Assert(false, "Out of sync read request");
11038 charsRead = 0;
11039 return true;
11042 charsRead = len;
11044 // stateObj._longlenleft is in bytes
11045 if ((stateObj._longlenleft >> 1) < (ulong)len)
11046 charsRead = (int)(stateObj._longlenleft >> 1);
11048 for (int ii = 0; ii < charsRead; ii++) {
11049 if (!stateObj.TryReadChar(out buff[offst + ii])) {
11050 return false;
11054 stateObj._longlenleft -= ((ulong)charsRead << 1);
11055 return true;
11058 internal int ReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserStateObject stateObj) {
11059 int charsRead;
11060 Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
11061 bool result = TryReadPlpUnicodeChars(ref buff, offst, len, stateObj, out charsRead);
11062 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
11063 return charsRead;
11066 // Reads the requested number of chars from a plp data stream, or the entire data if
11067 // requested length is -1 or larger than the actual length of data. First call to this method
11068 // should be preceeded by a call to ReadPlpLength or ReadDataLength.
11069 // Returns the actual chars read.
11070 internal bool TryReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserStateObject stateObj, out int totalCharsRead) {
11071 int charsRead = 0;
11072 int charsLeft = 0;
11073 char[] newbuf;
11075 if (stateObj._longlen == 0) {
11076 Debug.Assert(stateObj._longlenleft == 0);
11077 totalCharsRead = 0;
11078 return true; // No data
11081 Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL),
11082 "Out of sync plp read request");
11084 Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!");
11085 charsLeft = len;
11087 // If total length is known up front, allocate the whole buffer in one shot instead of realloc'ing and copying over each time
11088 if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN) {
11089 buff = new char[(int)Math.Min((int)stateObj._longlen, len)];
11092 if (stateObj._longlenleft == 0) {
11093 ulong ignored;
11094 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11095 totalCharsRead = 0;
11096 return false;
11098 if (stateObj._longlenleft == 0) { // Data read complete
11099 totalCharsRead = 0;
11100 return true;
11104 totalCharsRead = 0;
11106 while (charsLeft > 0) {
11107 charsRead = (int)Math.Min((stateObj._longlenleft + 1) >> 1, (ulong)charsLeft);
11108 if ((buff == null) || (buff.Length < (offst + charsRead))) {
11109 // Grow the array
11110 newbuf = new char[offst + charsRead];
11111 if (buff != null) {
11112 Buffer.BlockCopy(buff, 0, newbuf, 0, offst*2);
11114 buff = newbuf;
11116 if (charsRead > 0) {
11117 if (!TryReadPlpUnicodeCharsChunk(buff, offst, charsRead, stateObj, out charsRead)) {
11118 return false;
11120 charsLeft -= charsRead;
11121 offst += charsRead;
11122 totalCharsRead += charsRead;
11124 // Special case single byte left
11125 if (stateObj._longlenleft == 1 && (charsLeft > 0)) {
11126 byte b1;
11127 if (!stateObj.TryReadByte(out b1)) {
11128 return false;
11130 stateObj._longlenleft--;
11131 ulong ignored;
11132 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11133 return false;
11135 Debug.Assert((stateObj._longlenleft != 0), "ReadPlpUnicodeChars: Odd byte left at the end!");
11136 byte b2;
11137 if (!stateObj.TryReadByte(out b2)) {
11138 return false;
11140 stateObj._longlenleft--;
11141 // Put it at the end of the array. At this point we know we have an extra byte.
11142 buff[offst] = (char)(((b2 & 0xff) << 8) + (b1 & 0xff));
11143 offst = checked((int)offst + 1);
11144 charsRead++;
11145 charsLeft--;
11146 totalCharsRead++;
11148 if (stateObj._longlenleft == 0) { // Read the next chunk or cleanup state if hit the end
11149 ulong ignored;
11150 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11151 return false;
11155 if (stateObj._longlenleft == 0) // Data read complete
11156 break;
11158 return true;
11161 internal int ReadPlpAnsiChars(ref char[] buff, int offst, int len, SqlMetaDataPriv metadata, TdsParserStateObject stateObj) {
11162 int charsRead = 0;
11163 int charsLeft = 0;
11164 int bytesRead = 0;
11165 int totalcharsRead = 0;
11167 if (stateObj._longlen == 0) {
11168 Debug.Assert(stateObj._longlenleft == 0);
11169 return 0; // No data
11172 Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL),
11173 "Out of sync plp read request");
11175 Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpAnsiChars()!");
11176 charsLeft = len;
11178 if (stateObj._longlenleft == 0) {
11179 stateObj.ReadPlpLength(false);
11180 if (stateObj._longlenleft == 0) {// Data read complete
11181 stateObj._plpdecoder = null;
11182 return 0;
11186 if (stateObj._plpdecoder == null) {
11187 Encoding enc = metadata.encoding;
11189 if (enc == null)
11191 if (null == _defaultEncoding) {
11192 ThrowUnsupportedCollationEncountered(stateObj);
11196 enc = _defaultEncoding;
11198 stateObj._plpdecoder = enc.GetDecoder();
11201 while (charsLeft > 0) {
11202 bytesRead = (int)Math.Min(stateObj._longlenleft, (ulong)charsLeft);
11203 if ((stateObj._bTmp == null) || (stateObj._bTmp.Length < bytesRead)) {
11204 // Grow the array
11205 stateObj._bTmp = new byte[bytesRead];
11208 bytesRead = stateObj.ReadPlpBytesChunk(stateObj._bTmp, 0, bytesRead);
11210 charsRead = stateObj._plpdecoder.GetChars(stateObj._bTmp, 0, bytesRead, buff, offst);
11211 charsLeft -= charsRead;
11212 offst += charsRead;
11213 totalcharsRead += charsRead;
11214 if (stateObj._longlenleft == 0) // Read the next chunk or cleanup state if hit the end
11215 stateObj.ReadPlpLength(false);
11217 if (stateObj._longlenleft == 0) { // Data read complete
11218 stateObj._plpdecoder = null;
11219 break;
11222 return (totalcharsRead);
11225 // ensure value is not null and does not have an NBC bit set for it before using this method
11226 internal ulong SkipPlpValue(ulong cb, TdsParserStateObject stateObj) {
11227 ulong skipped;
11228 Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
11229 bool result = TrySkipPlpValue(cb, stateObj, out skipped);
11230 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
11231 return skipped;
11234 internal bool TrySkipPlpValue(ulong cb, TdsParserStateObject stateObj, out ulong totalBytesSkipped) {
11235 // Read and skip cb bytes or until ReadPlpLength returns 0.
11236 int bytesSkipped;
11237 totalBytesSkipped = 0;
11239 if (stateObj._longlenleft == 0) {
11240 ulong ignored;
11241 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11242 return false;
11246 while ((totalBytesSkipped < cb) &&
11247 (stateObj._longlenleft > 0)) {
11248 if (stateObj._longlenleft > Int32.MaxValue)
11249 bytesSkipped = Int32.MaxValue;
11250 else
11251 bytesSkipped = (int)stateObj._longlenleft;
11252 bytesSkipped = ((cb - totalBytesSkipped) < (ulong) bytesSkipped) ? (int)(cb - totalBytesSkipped) : bytesSkipped;
11254 if (!stateObj.TrySkipBytes(bytesSkipped)) {
11255 return false;
11257 stateObj._longlenleft -= (ulong)bytesSkipped;
11258 totalBytesSkipped += (ulong)bytesSkipped;
11260 if (stateObj._longlenleft == 0) {
11261 ulong ignored;
11262 if (!stateObj.TryReadPlpLength(false, out ignored)) {
11263 return false;
11268 return true;
11271 internal ulong PlpBytesLeft(TdsParserStateObject stateObj) {
11272 if ((stateObj._longlen != 0) && (stateObj._longlenleft == 0))
11273 stateObj.ReadPlpLength(false);
11275 return stateObj._longlenleft;
11278 internal bool TryPlpBytesLeft(TdsParserStateObject stateObj, out ulong left) {
11279 if ((stateObj._longlen != 0) && (stateObj._longlenleft == 0)) {
11280 if (!stateObj.TryReadPlpLength(false, out left)) {
11281 return false;
11285 left = stateObj._longlenleft;
11286 return true;
11289 private const ulong _indeterminateSize = 0xffffffffffffffff; // Represents unknown size
11291 internal ulong PlpBytesTotalLength(TdsParserStateObject stateObj) {
11292 if (stateObj._longlen == TdsEnums.SQL_PLP_UNKNOWNLEN)
11293 return _indeterminateSize;
11294 else if (stateObj._longlen == TdsEnums.SQL_PLP_NULL)
11295 return 0;
11297 return stateObj._longlen;
11300 const string StateTraceFormatString = "\n\t"
11301 + " _physicalStateObj = {0}\n\t"
11302 + " _pMarsPhysicalConObj = {1}\n\t"
11303 + " _state = {2}\n\t"
11304 + " _server = {3}\n\t"
11305 + " _fResetConnection = {4}\n\t"
11306 + " _defaultCollation = {5}\n\t"
11307 + " _defaultCodePage = {6}\n\t"
11308 + " _defaultLCID = {7}\n\t"
11309 + " _defaultEncoding = {8}\n\t"
11310 + " _encryptionOption = {10}\n\t"
11311 + " _currentTransaction = {11}\n\t"
11312 + " _pendingTransaction = {12}\n\t"
11313 + " _retainedTransactionId = {13}\n\t"
11314 + " _nonTransactedOpenResultCount = {14}\n\t"
11315 + " _connHandler = {15}\n\t"
11316 + " _fMARS = {16}\n\t"
11317 + " _sessionPool = {17}\n\t"
11318 + " _isShiloh = {18}\n\t"
11319 + " _isShilohSP1 = {19}\n\t"
11320 + " _isYukon = {20}\n\t"
11321 + " _sniSpnBuffer = {21}\n\t"
11322 + " _errors = {22}\n\t"
11323 + " _warnings = {23}\n\t"
11324 + " _attentionErrors = {24}\n\t"
11325 + " _attentionWarnings = {25}\n\t"
11326 + " _statistics = {26}\n\t"
11327 + " _statisticsIsInTransaction = {27}\n\t"
11328 + " _fPreserveTransaction = {28}"
11329 + " _fParallel = {29}"
11331 internal string TraceString() {
11332 return String.Format(/*IFormatProvider*/ null,
11333 StateTraceFormatString,
11334 null == _physicalStateObj,
11335 null == _pMarsPhysicalConObj,
11336 _state,
11337 _server,
11338 _fResetConnection,
11339 null == _defaultCollation ? "(null)" : _defaultCollation.TraceString(),
11340 _defaultCodePage,
11341 _defaultLCID,
11342 TraceObjectClass(_defaultEncoding),
11344 _encryptionOption,
11345 null == _currentTransaction ? "(null)" : _currentTransaction.TraceString(),
11346 null == _pendingTransaction ? "(null)" : _pendingTransaction.TraceString(),
11347 _retainedTransactionId,
11348 _nonTransactedOpenResultCount,
11349 null == _connHandler ? "(null)" : _connHandler.ObjectID.ToString((IFormatProvider)null),
11350 _fMARS,
11351 null == _sessionPool ? "(null)" : _sessionPool.TraceString(),
11352 _isShiloh,
11353 _isShilohSP1,
11354 _isYukon,
11355 null == _sniSpnBuffer ? "(null)" : _sniSpnBuffer.Length.ToString((IFormatProvider)null),
11356 _physicalStateObj != null ? "(null)" : _physicalStateObj.ErrorCount.ToString((IFormatProvider)null),
11357 _physicalStateObj != null ? "(null)" : _physicalStateObj.WarningCount.ToString((IFormatProvider)null),
11358 _physicalStateObj != null ? "(null)" : _physicalStateObj.PreAttentionErrorCount.ToString((IFormatProvider)null),
11359 _physicalStateObj != null ? "(null)" : _physicalStateObj.PreAttentionWarningCount.ToString((IFormatProvider)null),
11360 null == _statistics,
11361 _statisticsIsInTransaction,
11362 _fPreserveTransaction,
11363 null == _connHandler ? "(null)" : _connHandler.ConnectionOptions.MultiSubnetFailover.ToString((IFormatProvider)null),
11364 null == _connHandler ? "(null)" : _connHandler.ConnectionOptions.TransparentNetworkIPResolution.ToString((IFormatProvider)null));
11367 private string TraceObjectClass(object instance) {
11368 if (null == instance) {
11369 return "(null)";
11371 else {
11372 return instance.GetType().ToString();
11375 } // tdsparser
11376 }//namespace