1 //------------------------------------------------------------------------------
2 // <copyright file="SqlCommand.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data.SqlClient {
11 using System.Collections;
12 using System.Collections.Generic;
13 using System.Collections.ObjectModel;
14 using System.ComponentModel;
15 using System.Configuration.Assemblies;
17 using System.Data.Common;
18 using System.Data.ProviderBase;
19 using System.Data.Sql;
20 using System.Data.SqlTypes;
21 using System.Diagnostics;
22 using System.Diagnostics.Tracing;
23 using System.Globalization;
26 using System.Reflection;
27 using System.Runtime.CompilerServices;
28 using System.Runtime.ConstrainedExecution;
29 using System.Runtime.Serialization.Formatters;
30 using System.Security.Permissions;
32 using System.Threading;
33 using System.Threading.Tasks;
34 using SysTx = System.Transactions;
37 using Microsoft.SqlServer.Server;
40 DefaultEvent("RecordsAffected"),
42 Designer("Microsoft.VSDesigner.Data.VS.SqlCommandDesigner, " + AssemblyRef.MicrosoftVSDesigner)
44 public sealed class SqlCommand : DbCommand, ICloneable {
46 private static int _objectTypeCount; // Bid counter
47 internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
49 private string _commandText;
50 private CommandType _commandType;
51 private int _commandTimeout = ADP.DefaultCommandTimeout;
52 private UpdateRowSource _updatedRowSource = UpdateRowSource.Both;
53 private bool _designTimeInvisible;
56 /// Indicates if the column encryption setting was set at-least once in the batch rpc mode, when using AddBatchCommand.
58 private bool _wasBatchModeColumnEncryptionSettingSetOnce;
61 /// Column Encryption Override. Defaults to SqlConnectionSetting, in which case
62 /// it will be Enabled if SqlConnectionOptions.IsColumnEncryptionSettingEnabled = true, Disabled if false.
63 /// This may also be used to set other behavior which overrides connection level setting.
65 private SqlCommandColumnEncryptionSetting _columnEncryptionSetting = SqlCommandColumnEncryptionSetting.UseConnectionSetting;
67 internal SqlDependency _sqlDep;
71 /// Force the client to sleep during sp_describe_parameter_encryption in the function TryFetchInputParameterEncryptionInfo.
73 private static bool _sleepDuringTryFetchInputParameterEncryptionInfo = false;
76 /// Force the client to sleep during sp_describe_parameter_encryption in the function RunExecuteReaderTds.
78 private static bool _sleepDuringRunExecuteReaderTdsForSpDescribeParameterEncryption = false;
81 /// Force the client to sleep during sp_describe_parameter_encryption after ReadDescribeEncryptionParameterResults.
83 private static bool _sleepAfterReadDescribeEncryptionParameterResults = false;
86 /// Internal flag for testing purposes that forces all queries to internally end async calls.
88 private static bool _forceInternalEndQuery = false;
92 // Against 7.0 Server (Sphinx) a prepare/unprepare requires an extra roundtrip to the server.
94 // From 8.0 (Shiloh) and above (Yukon) the preparation can be done as part of the command execution.
96 private enum EXECTYPE {
97 UNPREPARED, // execute unprepared commands, all server versions (results in sp_execsql call)
98 PREPAREPENDING, // prepare and execute command, 8.0 and above only (results in sp_prepexec call)
99 PREPARED, // execute prepared commands, all server versions (results in sp_exec call)
105 // On 8.0 and above the Prepared state cannot be left. Once a command is prepared it will always be prepared.
106 // A change in parameters, commandtext etc (IsDirty) automatically causes a hidden prepare
108 // _inPrepare will be set immediately before the actual prepare is done.
109 // The OnReturnValue function will test this flag to determine whether the returned value is a _prepareHandle or something else.
111 // _prepareHandle - the handle of a prepared command. Apparently there can be multiple prepared commands at a time - a feature that we do not support yet.
113 private bool _inPrepare = false;
114 private int _prepareHandle = -1;
115 private bool _hiddenPrepare = false;
116 private int _preparedConnectionCloseCount = -1;
117 private int _preparedConnectionReconnectCount = -1;
119 private SqlParameterCollection _parameters;
120 private SqlConnection _activeConnection;
121 private bool _dirty = false; // true if the user changes the commandtext or number of parameters after the command is already prepared
122 private EXECTYPE _execType = EXECTYPE.UNPREPARED; // by default, assume the user is not sharing a connection so the command has not been prepared
123 private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes
124 private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes
126 // cut down on object creation and cache all these
128 private _SqlMetaDataSet _cachedMetaData;
130 // Last TaskCompletionSource for reconnect task - use for cancellation only
131 TaskCompletionSource<object> _reconnectionCompletionSource = null;
134 static internal int DebugForceAsyncWriteDelay { get; set; }
136 internal bool InPrepare {
143 /// Return if column encryption setting is enabled.
144 /// The order in the below if is important since _activeConnection.Parser can throw if the
145 /// underlying tds connection is closed and we don't want to change the behavior for folks
146 /// not trying to use transparent parameter encryption i.e. who don't use (SqlCommandColumnEncryptionSetting.Enabled or _activeConnection.IsColumnEncryptionSettingEnabled) here.
148 internal bool IsColumnEncryptionEnabled {
150 return (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled
151 || (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && _activeConnection.IsColumnEncryptionSettingEnabled))
152 && _activeConnection.Parser != null
153 && _activeConnection.Parser.IsColumnEncryptionSupported;
157 // Cached info for async executions
158 private class CachedAsyncState {
159 private int _cachedAsyncCloseCount = -1; // value of the connection's CloseCount property when the asyncResult was set; tracks when connections are closed after an async operation
160 private TaskCompletionSource<object> _cachedAsyncResult = null;
161 private SqlConnection _cachedAsyncConnection = null; // Used to validate that the connection hasn't changed when end the connection;
162 private SqlDataReader _cachedAsyncReader = null;
163 private RunBehavior _cachedRunBehavior = RunBehavior.ReturnImmediately;
164 private string _cachedSetOptions = null;
165 private string _cachedEndMethod = null;
167 internal CachedAsyncState () {
170 internal SqlDataReader CachedAsyncReader {
171 get {return _cachedAsyncReader;}
173 internal RunBehavior CachedRunBehavior {
174 get {return _cachedRunBehavior;}
176 internal string CachedSetOptions {
177 get {return _cachedSetOptions;}
179 internal bool PendingAsyncOperation {
180 get {return (null != _cachedAsyncResult);}
182 internal string EndMethodName {
183 get { return _cachedEndMethod; }
186 internal bool IsActiveConnectionValid(SqlConnection activeConnection) {
187 return (_cachedAsyncConnection == activeConnection && _cachedAsyncCloseCount == activeConnection.CloseCount);
190 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
191 internal void ResetAsyncState() {
192 _cachedAsyncCloseCount = -1;
193 _cachedAsyncResult = null;
194 if (_cachedAsyncConnection != null) {
195 _cachedAsyncConnection.AsyncCommandInProgress = false;
196 _cachedAsyncConnection = null;
198 _cachedAsyncReader = null;
199 _cachedRunBehavior = RunBehavior.ReturnImmediately;
200 _cachedSetOptions = null;
201 _cachedEndMethod = null;
204 internal void SetActiveConnectionAndResult(TaskCompletionSource<object> completion, string endMethod, SqlConnection activeConnection) {
205 Debug.Assert(activeConnection != null, "Unexpected null connection argument on SetActiveConnectionAndResult!");
206 TdsParser parser = activeConnection.Parser;
207 if ((parser == null) || (parser.State == TdsParserState.Closed) || (parser.State == TdsParserState.Broken)) {
208 throw ADP.ClosedConnectionError();
211 _cachedAsyncCloseCount = activeConnection.CloseCount;
212 _cachedAsyncResult = completion;
213 if (null != activeConnection && !parser.MARSOn) {
214 if (activeConnection.AsyncCommandInProgress)
215 throw SQL.MARSUnspportedOnConnection();
217 _cachedAsyncConnection = activeConnection;
219 // Should only be needed for non-MARS, but set anyways.
220 _cachedAsyncConnection.AsyncCommandInProgress = true;
221 _cachedEndMethod = endMethod;
224 internal void SetAsyncReaderState (SqlDataReader ds, RunBehavior runBehavior, string optionSettings) {
225 _cachedAsyncReader = ds;
226 _cachedRunBehavior = runBehavior;
227 _cachedSetOptions = optionSettings;
231 CachedAsyncState _cachedAsyncState = null;
233 private CachedAsyncState cachedAsyncState {
235 if (_cachedAsyncState == null) {
236 _cachedAsyncState = new CachedAsyncState ();
238 return _cachedAsyncState;
242 // sql reader will pull this value out for each NextResult call. It is not cumulative
243 // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches
244 internal int _rowsAffected = -1; // rows affected by the command
246 // number of rows affected by sp_describe_parameter_encryption.
247 // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise.
248 private int _rowsAffectedBySpDescribeParameterEncryption = -1;
250 private SqlNotificationRequest _notification;
251 private bool _notificationAutoEnlist = true; // Notifications auto enlistment is turned on by default
253 // transaction support
254 private SqlTransaction _transaction;
256 private StatementCompletedEventHandler _statementCompletedEventHandler;
258 private TdsParserStateObject _stateObj; // this is the TDS session we're using.
260 // Volatile bool used to synchronize with cancel thread the state change of an executing
261 // command going from pre-processing to obtaining a stateObject. The cancel synchronization
262 // we require in the command is only from entering an Execute* API to obtaining a
263 // stateObj. Once a stateObj is successfully obtained, cancel synchronization is handled
264 // by the stateObject.
265 private volatile bool _pendingCancel;
267 private bool _batchRPCMode;
268 private List<_SqlRPC> _RPCList;
269 private _SqlRPC[] _SqlRPCBatchArray;
270 private _SqlRPC[] _sqlRPCParameterEncryptionReqArray;
271 private List<SqlParameterCollection> _parameterCollectionList;
272 private int _currentlyExecutingBatch;
275 /// This variable is used to keep track of which RPC batch's results are being read when reading the results of
276 /// describe parameter encryption RPC requests in BatchRPCMode.
278 private int _currentlyExecutingDescribeParameterEncryptionRPC;
281 /// A flag to indicate if we have in-progress describe parameter encryption RPC requests.
282 /// Reset to false when completed.
284 private bool _isDescribeParameterEncryptionRPCCurrentlyInProgress;
287 /// Return the flag that indicates if describe parameter encryption RPC requests are in-progress.
289 internal bool IsDescribeParameterEncryptionRPCCurrentlyInProgress {
291 return _isDescribeParameterEncryptionRPCCurrentlyInProgress;
296 /// A flag to indicate if EndExecute was already initiated by the Begin call.
298 private volatile bool _internalEndExecuteInitiated;
301 /// A flag to indicate whether we postponed caching the query metadata for this command.
303 internal bool CachingQueryMetadataPostponed { get; set; }
306 // Smi execution-specific stuff
308 sealed private class CommandEventSink : SmiEventSink_Default {
309 private SqlCommand _command;
311 internal CommandEventSink( SqlCommand command ) : base( ) {
315 internal override void StatementCompleted( int rowsAffected ) {
316 if (Bid.AdvancedOn) {
317 Bid.Trace("<sc.SqlCommand.CommandEventSink.StatementCompleted|ADV> %d#, rowsAffected=%d.\n", _command.ObjectID, rowsAffected);
319 _command.InternalRecordsAffected = rowsAffected;
328 internal override void BatchCompleted() {
329 if (Bid.AdvancedOn) {
330 Bid.Trace("<sc.SqlCommand.CommandEventSink.BatchCompleted|ADV> %d#.\n", _command.ObjectID);
334 internal override void ParametersAvailable( SmiParameterMetaData[] metaData, ITypedGettersV3 parameterValues ) {
335 if (Bid.AdvancedOn) {
336 Bid.Trace("<sc.SqlCommand.CommandEventSink.ParametersAvailable|ADV> %d# metaData.Length=%d.\n", _command.ObjectID, (null!=metaData)?metaData.Length:-1);
338 if (null != metaData) {
339 for (int i=0; i < metaData.Length; i++) {
340 Bid.Trace("<sc.SqlCommand.CommandEventSink.ParametersAvailable|ADV> %d#, metaData[%d] is %ls%ls\n",
341 _command.ObjectID, i, metaData[i].GetType().ToString(), metaData[i].TraceString());
345 Debug.Assert(SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.YukonVersion);
346 _command.OnParametersAvailableSmi( metaData, parameterValues );
349 internal override void ParameterAvailable(SmiParameterMetaData metaData, SmiTypedGetterSetter parameterValues, int ordinal)
351 if (Bid.AdvancedOn) {
352 if (null != metaData) {
353 Bid.Trace("<sc.SqlCommand.CommandEventSink.ParameterAvailable|ADV> %d#, metaData[%d] is %ls%ls\n",
354 _command.ObjectID, ordinal, metaData.GetType().ToString(), metaData.TraceString());
357 Debug.Assert(SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion);
358 _command.OnParameterAvailableSmi(metaData, parameterValues, ordinal);
362 private SmiContext _smiRequestContext; // context that _smiRequest came from
363 private CommandEventSink _smiEventSink;
364 private SmiEventSink_DeferedProcessing _outParamEventSink;
366 private CommandEventSink EventSink {
368 if ( null == _smiEventSink ) {
369 _smiEventSink = new CommandEventSink( this );
372 _smiEventSink.Parent = InternalSmiConnection.CurrentEventSink;
373 return _smiEventSink;
377 private SmiEventSink_DeferedProcessing OutParamEventSink {
379 if (null == _outParamEventSink) {
380 _outParamEventSink = new SmiEventSink_DeferedProcessing(EventSink);
383 _outParamEventSink.Parent = EventSink;
386 return _outParamEventSink;
391 public SqlCommand() : base() {
392 GC.SuppressFinalize(this);
395 public SqlCommand(string cmdText) : this() {
396 CommandText = cmdText;
399 public SqlCommand(string cmdText, SqlConnection connection) : this() {
400 CommandText = cmdText;
401 Connection = connection;
404 public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction) : this() {
405 CommandText = cmdText;
406 Connection = connection;
407 Transaction = transaction;
410 public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction, SqlCommandColumnEncryptionSetting columnEncryptionSetting) : this() {
411 CommandText = cmdText;
412 Connection = connection;
413 Transaction = transaction;
414 _columnEncryptionSetting = columnEncryptionSetting;
417 private SqlCommand(SqlCommand from) : this() { // Clone
418 CommandText = from.CommandText;
419 CommandTimeout = from.CommandTimeout;
420 CommandType = from.CommandType;
421 Connection = from.Connection;
422 DesignTimeVisible = from.DesignTimeVisible;
423 Transaction = from.Transaction;
424 UpdatedRowSource = from.UpdatedRowSource;
425 _columnEncryptionSetting = from.ColumnEncryptionSetting;
427 SqlParameterCollection parameters = Parameters;
428 foreach(object parameter in from.Parameters) {
429 parameters.Add((parameter is ICloneable) ? (parameter as ICloneable).Clone() : parameter);
435 Editor("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
436 ResCategoryAttribute(Res.DataCategory_Data),
437 ResDescriptionAttribute(Res.DbCommand_Connection),
439 new public SqlConnection Connection {
441 return _activeConnection;
444 // Don't allow the connection to be changed while in a async opperation.
445 if (_activeConnection != value && _activeConnection != null) { // If new value...
446 if (cachedAsyncState.PendingAsyncOperation) { // If in pending async state, throw.
447 throw SQL.CannotModifyPropertyAsyncOperationInProgress(SQL.Connection);
451 // Check to see if the currently set transaction has completed. If so,
452 // null out our local reference.
453 if (null != _transaction && _transaction.Connection == null) {
457 // If the connection has changes, then the request context may have changed as well
458 _smiRequestContext = null;
460 // Command is no longer prepared on new connection, cleanup prepare status
462 if (_activeConnection != value && _activeConnection != null) {
463 RuntimeHelpers.PrepareConstrainedRegions();
466 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
467 RuntimeHelpers.PrepareConstrainedRegions();
469 tdsReliabilitySection.Start();
476 tdsReliabilitySection.Stop();
480 catch (System.OutOfMemoryException) {
481 _activeConnection.InnerConnection.DoomThisConnection();
484 catch (System.StackOverflowException) {
485 _activeConnection.InnerConnection.DoomThisConnection();
488 catch (System.Threading.ThreadAbortException) {
489 _activeConnection.InnerConnection.DoomThisConnection();
493 // we do not really care about errors in unprepare (may be the old connection went bad)
496 // clean prepare status (even successfull Unprepare does not do that)
498 _execType = EXECTYPE.UNPREPARED;
503 _activeConnection = value; //
505 Bid.Trace("<sc.SqlCommand.set_Connection|API> %d#, %d#\n", ObjectID, ((null != value) ? value.ObjectID : -1));
509 override protected DbConnection DbConnection { // V1.2.3300
514 Connection = (SqlConnection)value;
518 private SqlInternalConnectionSmi InternalSmiConnection {
520 return (SqlInternalConnectionSmi)_activeConnection.InnerConnection;
524 private SqlInternalConnectionTds InternalTdsConnection {
526 return (SqlInternalConnectionTds)_activeConnection.InnerConnection;
530 private bool IsShiloh {
532 Debug.Assert(_activeConnection != null, "The active connection is null!");
533 if (_activeConnection == null)
535 return _activeConnection.IsShiloh;
541 ResCategoryAttribute(Res.DataCategory_Notification),
542 ResDescriptionAttribute(Res.SqlCommand_NotificationAutoEnlist),
544 public bool NotificationAutoEnlist {
546 return _notificationAutoEnlist;
549 _notificationAutoEnlist = value;
555 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), // MDAC 90471
556 ResCategoryAttribute(Res.DataCategory_Notification),
557 ResDescriptionAttribute(Res.SqlCommand_Notification),
559 public SqlNotificationRequest Notification {
561 return _notification;
564 Bid.Trace("<sc.SqlCommand.set_Notification|API> %d#\n", ObjectID);
566 _notification = value;
571 internal SqlStatistics Statistics {
573 if (null != _activeConnection) {
574 if (_activeConnection.StatisticsEnabled) {
575 return _activeConnection.Statistics;
584 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
585 ResDescriptionAttribute(Res.DbCommand_Transaction),
587 new public SqlTransaction Transaction {
589 // if the transaction object has been zombied, just return null
590 if ((null != _transaction) && (null == _transaction.Connection)) { // MDAC 72720
596 // Don't allow the transaction to be changed while in a async opperation.
597 if (_transaction != value && _activeConnection != null) { // If new value...
598 if (cachedAsyncState.PendingAsyncOperation) { // If in pending async state, throw
599 throw SQL.CannotModifyPropertyAsyncOperationInProgress(SQL.Transaction);
604 Bid.Trace("<sc.SqlCommand.set_Transaction|API> %d#\n", ObjectID);
605 _transaction = value;
609 override protected DbTransaction DbTransaction { // V1.2.3300
614 Transaction = (SqlTransaction)value;
620 Editor("Microsoft.VSDesigner.Data.SQL.Design.SqlCommandTextEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
621 RefreshProperties(RefreshProperties.All), // MDAC 67707
622 ResCategoryAttribute(Res.DataCategory_Data),
623 ResDescriptionAttribute(Res.DbCommand_CommandText),
625 override public string CommandText { // V1.2.3300, XXXCommand V1.0.5000
627 string value = _commandText;
628 return ((null != value) ? value : ADP.StrEmpty);
632 Bid.Trace("<sc.SqlCommand.set_CommandText|API> %d#, '", ObjectID);
633 Bid.PutStr(value); // Use PutStr to write out entire string
636 if (0 != ADP.SrcCompare(_commandText, value)) {
638 _commandText = value;
645 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
646 ResCategoryAttribute(Res.DataCategory_Data),
647 ResDescriptionAttribute(Res.TCE_SqlCommand_ColumnEncryptionSetting),
649 public SqlCommandColumnEncryptionSetting ColumnEncryptionSetting {
651 return _columnEncryptionSetting;
656 ResCategoryAttribute(Res.DataCategory_Data),
657 ResDescriptionAttribute(Res.DbCommand_CommandTimeout),
659 override public int CommandTimeout { // V1.2.3300, XXXCommand V1.0.5000
661 return _commandTimeout;
664 Bid.Trace("<sc.SqlCommand.set_CommandTimeout|API> %d#, %d\n", ObjectID, value);
666 throw ADP.InvalidCommandTimeout(value);
668 if (value != _commandTimeout) {
670 _commandTimeout = value;
675 public void ResetCommandTimeout() { // V1.2.3300
676 if (ADP.DefaultCommandTimeout != _commandTimeout) {
678 _commandTimeout = ADP.DefaultCommandTimeout;
682 private bool ShouldSerializeCommandTimeout() { // V1.2.3300
683 return (ADP.DefaultCommandTimeout != _commandTimeout);
687 DefaultValue(System.Data.CommandType.Text),
688 RefreshProperties(RefreshProperties.All),
689 ResCategoryAttribute(Res.DataCategory_Data),
690 ResDescriptionAttribute(Res.DbCommand_CommandType),
692 override public CommandType CommandType { // V1.2.3300, XXXCommand V1.0.5000
694 CommandType cmdType = _commandType;
695 return ((0 != cmdType) ? cmdType : CommandType.Text);
698 Bid.Trace("<sc.SqlCommand.set_CommandType|API> %d#, %d{ds.CommandType}\n", ObjectID, (int)value);
699 if (_commandType != value) {
700 switch(value) { // @perfnote: Enum.IsDefined
701 case CommandType.Text:
702 case CommandType.StoredProcedure:
704 _commandType = value;
706 case System.Data.CommandType.TableDirect:
707 throw SQL.NotSupportedCommandType(value);
709 throw ADP.InvalidCommandType(value);
715 // @devnote: By default, the cmd object is visible on the design surface (i.e. VS7 Server Tray)
716 // to limit the number of components that clutter the design surface,
717 // when the DataAdapter design wizard generates the insert/update/delete commands it will
718 // set the DesignTimeVisible property to false so that cmds won't appear as individual objects
723 EditorBrowsableAttribute(EditorBrowsableState.Never),
725 public override bool DesignTimeVisible { // V1.2.3300, XXXCommand V1.0.5000
727 return !_designTimeInvisible;
730 _designTimeInvisible = !value;
731 TypeDescriptor.Refresh(this); // VS7 208845
736 DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
737 ResCategoryAttribute(Res.DataCategory_Data),
738 ResDescriptionAttribute(Res.DbCommand_Parameters),
740 new public SqlParameterCollection Parameters {
742 if (null == this._parameters) {
743 // delay the creation of the SqlParameterCollection
744 // until user actually uses the Parameters property
745 this._parameters = new SqlParameterCollection();
747 return this._parameters;
751 override protected DbParameterCollection DbParameterCollection { // V1.2.3300
758 DefaultValue(System.Data.UpdateRowSource.Both),
759 ResCategoryAttribute(Res.DataCategory_Update),
760 ResDescriptionAttribute(Res.DbCommand_UpdatedRowSource),
762 override public UpdateRowSource UpdatedRowSource { // V1.2.3300, XXXCommand V1.0.5000
764 return _updatedRowSource;
767 switch(value) { // @perfnote: Enum.IsDefined
768 case UpdateRowSource.None:
769 case UpdateRowSource.OutputParameters:
770 case UpdateRowSource.FirstReturnedRecord:
771 case UpdateRowSource.Both:
772 _updatedRowSource = value;
775 throw ADP.InvalidUpdateRowSource(value);
781 ResCategoryAttribute(Res.DataCategory_StatementCompleted),
782 ResDescriptionAttribute(Res.DbCommand_StatementCompleted),
784 public event StatementCompletedEventHandler StatementCompleted {
786 _statementCompletedEventHandler += value;
789 _statementCompletedEventHandler -= value;
793 internal void OnStatementCompleted(int recordCount) { // V1.2.3300
794 if (0 <= recordCount) {
795 StatementCompletedEventHandler handler = _statementCompletedEventHandler;
796 if (null != handler) {
798 Bid.Trace("<sc.SqlCommand.OnStatementCompleted|INFO> %d#, recordCount=%d\n", ObjectID, recordCount);
799 handler(this, new StatementCompletedEventArgs(recordCount));
803 if (!ADP.IsCatchableOrSecurityExceptionType(e)) {
807 ADP.TraceExceptionWithoutRethrow(e);
813 private void PropertyChanging() { // also called from SqlParameterCollection
817 override public void Prepare() {
818 SqlConnection.ExecutePermission.Demand();
820 // Reset _pendingCancel upon entry into any Execute - used to synchronize state
821 // between entry into Execute* API and the thread obtaining the stateObject.
822 _pendingCancel = false;
824 // Context connection's prepare is a no-op
825 if (null != _activeConnection && _activeConnection.IsContextConnection) {
829 SqlStatistics statistics = null;
831 Bid.ScopeEnter(out hscp, "<sc.SqlCommand.Prepare|API> %d#", ObjectID);
832 Bid.CorrelationTrace("<sc.SqlCommand.Prepare|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
833 statistics = SqlStatistics.StartTimer(Statistics);
835 // only prepare if batch with parameters
838 this.IsPrepared && !this.IsDirty
839 || (this.CommandType == CommandType.StoredProcedure)
841 (System.Data.CommandType.Text == this.CommandType)
842 && (0 == GetParameterCount (_parameters))
846 if (null != Statistics) {
847 Statistics.SafeIncrement (ref Statistics._prepares);
849 _hiddenPrepare = false;
852 // Validate the command outside of the try\catch to avoid putting the _stateObj on error
853 ValidateCommand(ADP.Prepare, false /*not async*/);
855 bool processFinallyBlock = true;
856 TdsParser bestEffortCleanupTarget = null;
857 RuntimeHelpers.PrepareConstrainedRegions();
859 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
861 // NOTE: The state object isn't actually needed for this, but it is still here for back-compat (since it does a bunch of checks)
864 // Loop through parameters ensuring that we do not have unspecified types, sizes, scales, or precisions
865 if (null != _parameters) {
866 int count = _parameters.Count;
867 for (int i = 0; i < count; ++i) {
868 _parameters[i].Prepare(this); // MDAC 67063
873 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
874 RuntimeHelpers.PrepareConstrainedRegions();
876 tdsReliabilitySection.Start();
884 tdsReliabilitySection.Stop();
888 catch (System.OutOfMemoryException e) {
889 processFinallyBlock = false;
890 _activeConnection.Abort(e);
893 catch (System.StackOverflowException e) {
894 processFinallyBlock = false;
895 _activeConnection.Abort(e);
898 catch (System.Threading.ThreadAbortException e) {
899 processFinallyBlock = false;
900 _activeConnection.Abort(e);
902 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
905 catch (Exception e) {
906 processFinallyBlock = ADP.IsCatchableExceptionType(e);
910 if (processFinallyBlock) {
911 _hiddenPrepare = false; // The command is now officially prepared
913 ReliablePutStateObject();
918 SqlStatistics.StopTimer(statistics);
919 Bid.ScopeLeave(ref hscp);
922 private void InternalPrepare() {
924 Debug.Assert(_cachedMetaData == null || !_dirty, "dirty query should not have cached metadata!"); // can have cached metadata if dirty because of parameters
926 // someone changed the command text or the parameter schema so we must unprepare the command
929 this.IsDirty = false;
931 Debug.Assert(_execType != EXECTYPE.PREPARED, "Invalid attempt to Prepare already Prepared command!");
932 Debug.Assert(_activeConnection != null, "must have an open connection to Prepare");
933 Debug.Assert(null != _stateObj, "TdsParserStateObject should not be null");
934 Debug.Assert(null != _stateObj.Parser, "TdsParser class should not be null in Command.Execute!");
935 Debug.Assert(_stateObj.Parser == _activeConnection.Parser, "stateobject parser not same as connection parser");
936 Debug.Assert(false == _inPrepare, "Already in Prepare cycle, this.inPrepare should be false!");
938 // remember that the user wants to do a prepare but don't actually do an rpc
939 _execType = EXECTYPE.PREPAREPENDING;
940 // Note the current close count of the connection - this will tell us if the connection has been closed between calls to Prepare() and Execute
941 _preparedConnectionCloseCount = _activeConnection.CloseCount;
942 _preparedConnectionReconnectCount = _activeConnection.ReconnectCount;
944 if (null != Statistics) {
945 Statistics.SafeIncrement(ref Statistics._prepares);
949 // SqlInternalConnectionTds needs to be able to unprepare a statement
950 internal void Unprepare() {
951 // Context connection's prepare is a no-op
952 if (_activeConnection.IsContextConnection) {
956 Debug.Assert(true == IsPrepared, "Invalid attempt to Unprepare a non-prepared command!");
957 Debug.Assert(_activeConnection != null, "must have an open connection to UnPrepare");
958 Debug.Assert(false == _inPrepare, "_inPrepare should be false!");
960 // @devnote: we're always falling back to Prepare pending
961 // @devnote: This seems broken because once the command is prepared it will - always - be a
962 // @devnote: prepared execution.
963 // @devnote: Even replacing the parameterlist with something completely different or
964 // @devnote: changing the commandtext to a non-parameterized query will result in prepared execution
966 // @devnote: We need to keep the behavior for backward compatibility though (non-breaking change)
968 _execType = EXECTYPE.PREPAREPENDING;
969 // Don't zero out the handle because we'll pass it in to sp_prepexec on the next prepare
970 // Unless the close count isn't the same as when we last prepared
971 if ((_activeConnection.CloseCount != _preparedConnectionCloseCount) || (_activeConnection.ReconnectCount != _preparedConnectionReconnectCount)) {
976 _cachedMetaData = null;
977 Bid.Trace("<sc.SqlCommand.Prepare|INFO> %d#, Command unprepared.\n", ObjectID);
981 // Cancel is supposed to be multi-thread safe.
982 // It doesn't make sense to verify the connection exists or that it is open during cancel
983 // because immediately after checkin the connection can be closed or removed via another thread.
985 override public void Cancel() {
987 Bid.ScopeEnter(out hscp, "<sc.SqlCommand.Cancel|API> %d#", ObjectID);
989 Bid.CorrelationTrace("<sc.SqlCommand.Cancel|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
991 SqlStatistics statistics = null;
993 statistics = SqlStatistics.StartTimer(Statistics);
995 // If we are in reconnect phase simply cancel the waiting task
996 var reconnectCompletionSource = _reconnectionCompletionSource;
997 if (reconnectCompletionSource != null) {
998 if (reconnectCompletionSource.TrySetCanceled()) {
1003 // the pending data flag means that we are awaiting a response or are in the middle of proccessing a response
1004 // if we have no pending data, then there is nothing to cancel
1005 // if we have pending data, but it is not a result of this command, then we don't cancel either. Note that
1006 // this model is implementable because we only allow one active command at any one time. This code
1007 // will have to change we allow multiple outstanding batches
1010 if (null == _activeConnection) {
1013 SqlInternalConnectionTds connection = (_activeConnection.InnerConnection as SqlInternalConnectionTds);
1014 if (null == connection) { // Fail with out locking
1018 // The lock here is to protect against the command.cancel / connection.close race condition
1019 // The SqlInternalConnectionTds is set to OpenBusy during close, once this happens the cast below will fail and
1020 // the command will no longer be cancelable. It might be desirable to be able to cancel the close opperation, but this is
1021 // outside of the scope of Whidbey RTM. See (SqlConnection::Close) for other lock.
1023 if (connection != (_activeConnection.InnerConnection as SqlInternalConnectionTds)) { // make sure the connection held on the active connection is what we have stored in our temp connection variable, if not between getting "connection" and takeing the lock, the connection has been closed
1027 TdsParser parser = connection.Parser;
1028 if (null == parser) {
1032 TdsParser bestEffortCleanupTarget = null;
1033 RuntimeHelpers.PrepareConstrainedRegions();
1036 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
1038 RuntimeHelpers.PrepareConstrainedRegions();
1040 tdsReliabilitySection.Start();
1044 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
1046 if (!_pendingCancel) { // Do nothing if aleady pending.
1047 // Before attempting actual cancel, set the _pendingCancel flag to false.
1048 // This denotes to other thread before obtaining stateObject from the
1049 // session pool that there is another thread wishing to cancel.
1050 // The period in question is between entering the ExecuteAPI and obtaining
1052 _pendingCancel = true;
1054 TdsParserStateObject stateObj = _stateObj;
1055 if (null != stateObj) {
1056 stateObj.Cancel(ObjectID);
1059 SqlDataReader reader = connection.FindLiveReader(this);
1060 if (reader != null) {
1061 reader.Cancel(ObjectID);
1068 tdsReliabilitySection.Stop();
1072 catch (System.OutOfMemoryException e) {
1073 _activeConnection.Abort(e);
1076 catch (System.StackOverflowException e) {
1077 _activeConnection.Abort(e);
1080 catch (System.Threading.ThreadAbortException e) {
1081 _activeConnection.Abort(e);
1082 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
1088 SqlStatistics.StopTimer(statistics);
1089 Bid.ScopeLeave(ref hscp);
1093 new public SqlParameter CreateParameter() {
1094 return new SqlParameter();
1097 override protected DbParameter CreateDbParameter() {
1098 return CreateParameter();
1101 override protected void Dispose(bool disposing) {
1102 if (disposing) { // release mananged objects
1104 // V1.0, V1.1 did not reset the Connection, Parameters, CommandText, WebData 100524
1105 //_parameters = null;
1106 //_activeConnection = null;
1107 //_statistics = null;
1108 //CommandText = null;
1109 _cachedMetaData = null;
1111 // release unmanaged objects
1112 base.Dispose(disposing);
1115 override public object ExecuteScalar() {
1116 SqlConnection.ExecutePermission.Demand();
1118 // Reset _pendingCancel upon entry into any Execute - used to synchronize state
1119 // between entry into Execute* API and the thread obtaining the stateObject.
1120 _pendingCancel = false;
1122 SqlStatistics statistics = null;
1124 Bid.ScopeEnter(out hscp, "<sc.SqlCommand.ExecuteScalar|API> %d#", ObjectID);
1125 Bid.CorrelationTrace("<sc.SqlCommand.ExecuteScalar|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1127 bool success = false;
1128 int? sqlExceptionNumber = null;
1130 statistics = SqlStatistics.StartTimer(Statistics);
1131 WriteBeginExecuteEvent();
1133 ds = RunExecuteReader(0, RunBehavior.ReturnImmediately, true, ADP.ExecuteScalar);
1134 object result = CompleteExecuteScalar(ds, false);
1138 catch (SqlException ex) {
1139 sqlExceptionNumber = ex.Number;
1143 SqlStatistics.StopTimer(statistics);
1144 Bid.ScopeLeave(ref hscp);
1145 WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous: true);
1149 private object CompleteExecuteScalar(SqlDataReader ds, bool returnSqlValue) {
1150 object retResult = null;
1154 if (ds.FieldCount > 0) {
1155 if (returnSqlValue) {
1156 retResult = ds.GetSqlValue(0);
1159 retResult = ds.GetValue(0);
1165 // clean off the wire
1172 override public int ExecuteNonQuery() {
1173 SqlConnection.ExecutePermission.Demand();
1175 // Reset _pendingCancel upon entry into any Execute - used to synchronize state
1176 // between entry into Execute* API and the thread obtaining the stateObject.
1177 _pendingCancel = false;
1179 SqlStatistics statistics = null;
1181 Bid.ScopeEnter(out hscp, "<sc.SqlCommand.ExecuteNonQuery|API> %d#", ObjectID);
1182 Bid.CorrelationTrace("<sc.SqlCommand.ExecuteNonQuery|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1183 bool success = false;
1184 int? sqlExceptionNumber = null;
1186 statistics = SqlStatistics.StartTimer(Statistics);
1187 WriteBeginExecuteEvent();
1189 InternalExecuteNonQuery(null, ADP.ExecuteNonQuery, false, CommandTimeout, out usedCache);
1191 return _rowsAffected;
1193 catch (SqlException ex) {
1194 sqlExceptionNumber = ex.Number;
1198 SqlStatistics.StopTimer(statistics);
1199 Bid.ScopeLeave(ref hscp);
1200 WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : true);
1204 // Handles in-proc execute-to-pipe functionality
1205 // Identical to ExecuteNonQuery
1206 internal void ExecuteToPipe( SmiContext pipeContext ) {
1207 SqlConnection.ExecutePermission.Demand();
1209 // Reset _pendingCancel upon entry into any Execute - used to synchronize state
1210 // between entry into Execute* API and the thread obtaining the stateObject.
1211 _pendingCancel = false;
1213 SqlStatistics statistics = null;
1215 Bid.ScopeEnter(out hscp, "<sc.SqlCommand.ExecuteToPipe|INFO> %d#", ObjectID);
1217 statistics = SqlStatistics.StartTimer(Statistics);
1219 InternalExecuteNonQuery(null, ADP.ExecuteNonQuery, true, CommandTimeout, out usedCache);
1222 SqlStatistics.StopTimer(statistics);
1223 Bid.ScopeLeave(ref hscp);
1227 [System.Security.Permissions.HostProtectionAttribute(ExternalThreading=true)]
1228 public IAsyncResult BeginExecuteNonQuery() {
1229 // BeginExecuteNonQuery will track ExecutionTime for us
1230 return BeginExecuteNonQuery(null, null);
1233 [System.Security.Permissions.HostProtectionAttribute(ExternalThreading=true)]
1234 public IAsyncResult BeginExecuteNonQuery(AsyncCallback callback, object stateObject) {
1235 Bid.CorrelationTrace("<sc.SqlCommand.BeginExecuteNonQuery|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1236 SqlConnection.ExecutePermission.Demand();
1237 return BeginExecuteNonQueryInternal(0, callback, stateObject, 0, inRetry: false);
1240 private IAsyncResult BeginExecuteNonQueryAsync(AsyncCallback callback, object stateObject) {
1241 return BeginExecuteNonQueryInternal(0, callback, stateObject, CommandTimeout, inRetry: false, asyncWrite:true);
1244 private IAsyncResult BeginExecuteNonQueryInternal(CommandBehavior behavior, AsyncCallback callback, object stateObject, int timeout, bool inRetry, bool asyncWrite = false) {
1245 TaskCompletionSource<object> globalCompletion = new TaskCompletionSource<object>(stateObject);
1246 TaskCompletionSource<object> localCompletion = new TaskCompletionSource<object>(stateObject);
1249 // Reset _pendingCancel upon entry into any Execute - used to synchronize state
1250 // between entry into Execute* API and the thread obtaining the stateObject.
1251 _pendingCancel = false;
1253 ValidateAsyncCommand(); // Special case - done outside of try/catches to prevent putting a stateObj
1254 // back into pool when we should not.
1257 SqlStatistics statistics = null;
1260 statistics = SqlStatistics.StartTimer(Statistics);
1261 WriteBeginExecuteEvent();
1265 try { // InternalExecuteNonQuery already has reliability block, but if failure will not put stateObj back into pool.
1266 Task execNQ = InternalExecuteNonQuery(localCompletion, ADP.BeginExecuteNonQuery, false, timeout, out usedCache, asyncWrite, inRetry: inRetry);
1267 if (execNQ != null) {
1268 AsyncHelper.ContinueTask(execNQ, localCompletion, () => BeginExecuteNonQueryInternalReadStage(localCompletion));
1271 BeginExecuteNonQueryInternalReadStage(localCompletion);
1274 catch (Exception e) {
1275 if (!ADP.IsCatchableOrSecurityExceptionType(e)) {
1276 // If not catchable - the connection has already been caught and doomed in RunExecuteReader.
1280 // For async, RunExecuteReader will never put the stateObj back into the pool, so do so now.
1281 ReliablePutStateObject();
1285 // When we use query caching for parameter encryption we need to retry on specific errors.
1286 // In these cases finalize the call internally and trigger a retry when needed.
1287 if (!TriggerInternalEndAndRetryIfNecessary(behavior, stateObject, timeout, ADP.EndExecuteNonQuery, usedCache, inRetry, asyncWrite, globalCompletion, localCompletion, InternalEndExecuteNonQuery, BeginExecuteNonQueryInternal)) {
1288 globalCompletion = localCompletion;
1291 // Add callback after work is done to avoid overlapping Begin\End methods
1292 if (callback != null) {
1293 globalCompletion.Task.ContinueWith((t) => callback(t), TaskScheduler.Default);
1296 return globalCompletion.Task;
1299 SqlStatistics.StopTimer(statistics);
1303 private void BeginExecuteNonQueryInternalReadStage(TaskCompletionSource<object> completion) {
1304 // Read SNI does not have catches for async exceptions, handle here.
1305 TdsParser bestEffortCleanupTarget = null;
1306 RuntimeHelpers.PrepareConstrainedRegions();
1309 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
1311 RuntimeHelpers.PrepareConstrainedRegions();
1313 tdsReliabilitySection.Start();
1317 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
1318 // must finish caching information before ReadSni which can activate the callback before returning
1319 cachedAsyncState.SetActiveConnectionAndResult(completion, ADP.EndExecuteNonQuery, _activeConnection);
1320 _stateObj.ReadSni(completion);
1324 tdsReliabilitySection.Stop();
1328 catch (System.OutOfMemoryException e) {
1329 _activeConnection.Abort(e);
1332 catch (System.StackOverflowException e) {
1333 _activeConnection.Abort(e);
1336 catch (System.Threading.ThreadAbortException e) {
1337 _activeConnection.Abort(e);
1338 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
1342 // Similarly, if an exception occurs put the stateObj back into the pool.
1343 // and reset async cache information to allow a second async execute
1344 if (null != _cachedAsyncState) {
1345 _cachedAsyncState.ResetAsyncState();
1347 ReliablePutStateObject();
1352 private void VerifyEndExecuteState(Task completionTask, String endMethod, bool fullCheckForColumnEncryption = false) {
1353 if (null == completionTask) {
1354 throw ADP.ArgumentNull("asyncResult");
1356 if (completionTask.IsCanceled) {
1357 if (_stateObj != null) {
1358 _stateObj.Parser.State = TdsParserState.Broken; // We failed to respond to attention, we have to quit!
1359 _stateObj.Parser.Connection.BreakConnection();
1360 _stateObj.Parser.ThrowExceptionAndWarning(_stateObj);
1363 Debug.Assert(_reconnectionCompletionSource == null || _reconnectionCompletionSource.Task.IsCanceled, "ReconnectCompletionSource should be null or cancelled");
1364 throw SQL.CR_ReconnectionCancelled();
1367 else if (completionTask.IsFaulted) {
1368 throw completionTask.Exception.InnerException;
1371 // If transparent parameter encryption was attempted, then we need to skip other checks like those on EndMethodName
1372 // since we want to wait for async results before checking those fields.
1373 if (IsColumnEncryptionEnabled && !fullCheckForColumnEncryption) {
1374 if (_activeConnection.State != ConnectionState.Open) {
1375 // If the connection is not 'valid' then it was closed while we were executing
1376 throw ADP.ClosedConnectionError();
1382 if (cachedAsyncState.EndMethodName == null) {
1383 throw ADP.MethodCalledTwice(endMethod);
1385 if (endMethod != cachedAsyncState.EndMethodName) {
1386 throw ADP.MismatchedAsyncResult(cachedAsyncState.EndMethodName, endMethod);
1388 if ((_activeConnection.State != ConnectionState.Open) || (!cachedAsyncState.IsActiveConnectionValid(_activeConnection))) {
1389 // If the connection is not 'valid' then it was closed while we were executing
1390 throw ADP.ClosedConnectionError();
1394 private void WaitForAsyncResults(IAsyncResult asyncResult, bool isInternal) {
1395 Task completionTask = (Task) asyncResult;
1396 if (!asyncResult.IsCompleted) {
1397 asyncResult.AsyncWaitHandle.WaitOne();
1400 if (_stateObj != null) {
1401 _stateObj._networkPacketTaskSource = null;
1404 // If this is an internal command we will decrement the count when the End method is actually called by the user.
1405 // If we are using Column Encryption and the previous task failed, the async count should have already been fixed up.
1406 // There is a generic issue in how we handle the async count because:
1407 // a) BeginExecute might or might not clean it up on failure.
1408 // b) In EndExecute, we check the task state before waiting and throw if it's failed, whereas if we wait we will always adjust the count.
1409 if (!isInternal && (!IsColumnEncryptionEnabled || !completionTask.IsFaulted)) {
1410 _activeConnection.GetOpenTdsConnection().DecrementAsyncCount();
1414 public int EndExecuteNonQuery(IAsyncResult asyncResult) {
1416 return EndExecuteNonQueryInternal(asyncResult);
1419 Bid.CorrelationTrace("<sc.SqlCommand.EndExecuteNonQuery|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1423 private void ThrowIfReconnectionHasBeenCanceled() {
1424 if (_stateObj == null) {
1425 var reconnectionCompletionSource = _reconnectionCompletionSource;
1426 if (reconnectionCompletionSource != null && reconnectionCompletionSource.Task.IsCanceled) {
1427 throw SQL.CR_ReconnectionCancelled();
1432 private int EndExecuteNonQueryAsync(IAsyncResult asyncResult) {
1433 Bid.CorrelationTrace("<sc.SqlCommand.EndExecuteNonQueryAsync|Info|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1434 Debug.Assert(!_internalEndExecuteInitiated || _stateObj == null);
1436 Exception asyncException = ((Task)asyncResult).Exception;
1437 if (asyncException != null) {
1438 // Leftover exception from the Begin...InternalReadStage
1439 ReliablePutStateObject();
1440 throw asyncException.InnerException;
1443 ThrowIfReconnectionHasBeenCanceled();
1444 // lock on _stateObj prevents ----s with close/cancel.
1445 // If we have already initiate the End call internally, we have already done that, so no point doing it again.
1446 if (!_internalEndExecuteInitiated) {
1448 return EndExecuteNonQueryInternal(asyncResult);
1452 return EndExecuteNonQueryInternal(asyncResult);
1457 private int EndExecuteNonQueryInternal(IAsyncResult asyncResult) {
1458 SqlStatistics statistics = null;
1459 bool success = false;
1460 int? sqlExceptionNumber = null;
1462 statistics = SqlStatistics.StartTimer(Statistics);
1463 int result = (int)InternalEndExecuteNonQuery(asyncResult, ADP.EndExecuteNonQuery, isInternal: false);
1467 catch (SqlException e) {
1468 sqlExceptionNumber = e.Number;
1469 if (cachedAsyncState != null) {
1470 cachedAsyncState.ResetAsyncState();
1473 // SqlException is always catchable
1474 ReliablePutStateObject();
1477 catch (Exception e) {
1478 if (cachedAsyncState != null) {
1479 cachedAsyncState.ResetAsyncState();
1481 if (ADP.IsCatchableExceptionType(e)) {
1482 ReliablePutStateObject();
1487 SqlStatistics.StopTimer(statistics);
1488 WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous: false);
1492 private object InternalEndExecuteNonQuery(IAsyncResult asyncResult, string endMethod, bool isInternal) {
1493 TdsParser bestEffortCleanupTarget = null;
1494 RuntimeHelpers.PrepareConstrainedRegions();
1498 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
1500 RuntimeHelpers.PrepareConstrainedRegions();
1502 tdsReliabilitySection.Start();
1506 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
1507 VerifyEndExecuteState((Task)asyncResult, endMethod);
1508 WaitForAsyncResults(asyncResult, isInternal);
1510 // If column encryption is enabled, also check the state after waiting for the task.
1511 // It would be better to do this for all cases, but avoiding for compatibility reasons.
1512 if (IsColumnEncryptionEnabled) {
1513 VerifyEndExecuteState((Task)asyncResult, endMethod, fullCheckForColumnEncryption: true);
1516 bool processFinallyBlock = true;
1518 // If this is not for internal usage, notify the dependency.
1519 // If we have already initiated the end internally, the reader should be ready, so just return the rows affected.
1523 if (_internalEndExecuteInitiated) {
1524 Debug.Assert(_stateObj == null);
1526 // Reset the state since we exit early.
1527 cachedAsyncState.ResetAsyncState();
1529 return _rowsAffected;
1533 CheckThrowSNIException();
1535 // only send over SQL Batch command if we are not a stored proc and have no parameters
1536 if ((System.Data.CommandType.Text == this.CommandType) && (0 == GetParameterCount(_parameters))) {
1539 Debug.Assert(_stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
1540 bool result = _stateObj.Parser.TryRun(RunBehavior.UntilDone, this, null, null, _stateObj, out dataReady);
1541 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
1544 // Don't reset the state for internal End. The user End will do that eventually.
1546 cachedAsyncState.ResetAsyncState();
1550 else { // otherwise, use a full-fledged execute that can handle params and stored procs
1551 SqlDataReader reader = CompleteAsyncExecuteReader(isInternal);
1552 if (null != reader) {
1557 catch (Exception e) {
1558 processFinallyBlock = ADP.IsCatchableExceptionType(e);
1562 if (processFinallyBlock) {
1567 Debug.Assert(null == _stateObj, "non-null state object in EndExecuteNonQuery");
1568 return _rowsAffected;
1572 tdsReliabilitySection.Stop();
1576 catch (System.OutOfMemoryException e) {
1577 _activeConnection.Abort(e);
1580 catch (System.StackOverflowException e) {
1581 _activeConnection.Abort(e);
1584 catch (System.Threading.ThreadAbortException e) {
1585 _activeConnection.Abort(e);
1586 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
1591 private Task InternalExecuteNonQuery(TaskCompletionSource<object> completion, string methodName, bool sendToPipe, int timeout, out bool usedCache, bool asyncWrite = false, bool inRetry = false) {
1592 bool async = (null != completion);
1595 SqlStatistics statistics = Statistics;
1598 TdsParser bestEffortCleanupTarget = null;
1599 RuntimeHelpers.PrepareConstrainedRegions();
1602 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
1604 RuntimeHelpers.PrepareConstrainedRegions();
1606 tdsReliabilitySection.Start();
1610 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
1611 // @devnote: this function may throw for an invalid connection
1612 // @devnote: returns false for empty command text
1614 ValidateCommand(methodName, async);
1616 CheckNotificationStateAndAutoEnlist(); // Only call after validate - requires non null connection!
1620 // only send over SQL Batch command if we are not a stored proc and have no parameters and not in batch RPC mode
1621 if ( _activeConnection.IsContextConnection ) {
1622 if (null != statistics) {
1623 statistics.SafeIncrement(ref statistics._unpreparedExecs);
1626 RunExecuteNonQuerySmi( sendToPipe );
1628 else if (!BatchRPCMode && (System.Data.CommandType.Text == this.CommandType) && (0 == GetParameterCount(_parameters))) {
1629 Debug.Assert( !sendToPipe, "trying to send non-context command to pipe" );
1630 if (null != statistics) {
1631 if (!this.IsDirty && this.IsPrepared) {
1632 statistics.SafeIncrement(ref statistics._preparedExecs);
1635 statistics.SafeIncrement(ref statistics._unpreparedExecs);
1639 // We should never get here for a retry since we only have retries for parameters.
1640 Debug.Assert(!inRetry);
1642 task = RunExecuteNonQueryTds(methodName, async, timeout, asyncWrite);
1644 else { // otherwise, use a full-fledged execute that can handle params and stored procs
1645 Debug.Assert( !sendToPipe, "trying to send non-context command to pipe" );
1646 Bid.Trace("<sc.SqlCommand.ExecuteNonQuery|INFO> %d#, Command executed as RPC.\n", ObjectID);
1647 SqlDataReader reader = RunExecuteReader(0, RunBehavior.UntilDone, false, methodName, completion, timeout, out task, out usedCache, asyncWrite, inRetry);
1650 task = AsyncHelper.CreateContinuationTask(task, () => reader.Close());
1657 Debug.Assert(async || null == _stateObj, "non-null state object in InternalExecuteNonQuery");
1662 tdsReliabilitySection.Stop();
1666 catch (System.OutOfMemoryException e) {
1667 _activeConnection.Abort(e);
1670 catch (System.StackOverflowException e) {
1671 _activeConnection.Abort(e);
1674 catch (System.Threading.ThreadAbortException e) {
1675 _activeConnection.Abort(e);
1676 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
1681 public XmlReader ExecuteXmlReader() {
1682 SqlConnection.ExecutePermission.Demand();
1684 // Reset _pendingCancel upon entry into any Execute - used to synchronize state
1685 // between entry into Execute* API and the thread obtaining the stateObject.
1686 _pendingCancel = false;
1688 SqlStatistics statistics = null;
1690 Bid.ScopeEnter(out hscp, "<sc.SqlCommand.ExecuteXmlReader|API> %d#", ObjectID);
1691 Bid.CorrelationTrace("<sc.SqlCommand.ExecuteXmlReader|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1692 bool success = false;
1693 int? sqlExceptionNumber = null;
1695 statistics = SqlStatistics.StartTimer(Statistics);
1696 WriteBeginExecuteEvent();
1698 // use the reader to consume metadata
1700 ds = RunExecuteReader(CommandBehavior.SequentialAccess, RunBehavior.ReturnImmediately, true, ADP.ExecuteXmlReader);
1701 XmlReader result = CompleteXmlReader(ds);
1705 catch (SqlException ex) {
1706 sqlExceptionNumber = ex.Number;
1710 SqlStatistics.StopTimer(statistics);
1711 Bid.ScopeLeave(ref hscp);
1712 WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : true);
1716 [System.Security.Permissions.HostProtectionAttribute(ExternalThreading=true)]
1717 public IAsyncResult BeginExecuteXmlReader() {
1718 // BeginExecuteXmlReader will track executiontime
1719 return BeginExecuteXmlReader(null, null);
1722 [System.Security.Permissions.HostProtectionAttribute(ExternalThreading=true)]
1723 public IAsyncResult BeginExecuteXmlReader(AsyncCallback callback, object stateObject) {
1724 Bid.CorrelationTrace("<sc.SqlCommand.BeginExecuteXmlReader|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1725 SqlConnection.ExecutePermission.Demand();
1726 return BeginExecuteXmlReaderInternal(CommandBehavior.SequentialAccess, callback, stateObject, 0, inRetry: false);
1729 private IAsyncResult BeginExecuteXmlReaderAsync(AsyncCallback callback, object stateObject) {
1730 return BeginExecuteXmlReaderInternal(CommandBehavior.SequentialAccess, callback, stateObject, CommandTimeout, inRetry: false, asyncWrite: true);
1733 private IAsyncResult BeginExecuteXmlReaderInternal(CommandBehavior behavior, AsyncCallback callback, object stateObject, int timeout, bool inRetry, bool asyncWrite = false) {
1734 TaskCompletionSource<object> globalCompletion = new TaskCompletionSource<object>(stateObject);
1735 TaskCompletionSource<object> localCompletion = new TaskCompletionSource<object>(stateObject);
1738 // Reset _pendingCancel upon entry into any Execute - used to synchronize state
1739 // between entry into Execute* API and the thread obtaining the stateObject.
1740 _pendingCancel = false;
1742 ValidateAsyncCommand(); // Special case - done outside of try/catches to prevent putting a stateObj
1743 // back into pool when we should not.
1746 SqlStatistics statistics = null;
1749 statistics = SqlStatistics.StartTimer(Statistics);
1750 WriteBeginExecuteEvent();
1755 try { // InternalExecuteNonQuery already has reliability block, but if failure will not put stateObj back into pool.
1756 RunExecuteReader(behavior, RunBehavior.ReturnImmediately, true, ADP.BeginExecuteXmlReader, localCompletion, timeout, out writeTask, out usedCache, asyncWrite, inRetry);
1758 catch (Exception e) {
1759 if (!ADP.IsCatchableOrSecurityExceptionType(e)) {
1760 // If not catchable - the connection has already been caught and doomed in RunExecuteReader.
1764 // For async, RunExecuteReader will never put the stateObj back into the pool, so do so now.
1765 ReliablePutStateObject();
1769 if (writeTask != null) {
1770 AsyncHelper.ContinueTask(writeTask, localCompletion, () => BeginExecuteXmlReaderInternalReadStage(localCompletion));
1773 BeginExecuteXmlReaderInternalReadStage(localCompletion);
1776 // When we use query caching for parameter encryption we need to retry on specific errors.
1777 // In these cases finalize the call internally and trigger a retry when needed.
1778 if (!TriggerInternalEndAndRetryIfNecessary(behavior, stateObject, timeout, ADP.EndExecuteXmlReader, usedCache, inRetry, asyncWrite, globalCompletion, localCompletion, InternalEndExecuteReader, BeginExecuteXmlReaderInternal)) {
1779 globalCompletion = localCompletion;
1782 // Add callback after work is done to avoid overlapping Begin\End methods
1783 if (callback != null) {
1784 globalCompletion.Task.ContinueWith((t) => callback(t), TaskScheduler.Default);
1786 return globalCompletion.Task;
1789 SqlStatistics.StopTimer(statistics);
1793 private void BeginExecuteXmlReaderInternalReadStage(TaskCompletionSource<object> completion) {
1794 Debug.Assert(completion != null,"Completion source should not be null");
1795 // Read SNI does not have catches for async exceptions, handle here.
1796 TdsParser bestEffortCleanupTarget = null;
1797 RuntimeHelpers.PrepareConstrainedRegions();
1800 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
1802 RuntimeHelpers.PrepareConstrainedRegions();
1804 tdsReliabilitySection.Start();
1808 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
1809 // must finish caching information before ReadSni which can activate the callback before returning
1810 cachedAsyncState.SetActiveConnectionAndResult(completion, ADP.EndExecuteXmlReader, _activeConnection);
1811 _stateObj.ReadSni(completion);
1815 tdsReliabilitySection.Stop();
1819 catch (System.OutOfMemoryException e) {
1820 _activeConnection.Abort(e);
1821 completion.TrySetException(e);
1824 catch (System.StackOverflowException e) {
1825 _activeConnection.Abort(e);
1826 completion.TrySetException(e);
1829 catch (System.Threading.ThreadAbortException e) {
1830 _activeConnection.Abort(e);
1831 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
1832 completion.TrySetException(e);
1835 catch (Exception e) {
1836 // Similarly, if an exception occurs put the stateObj back into the pool.
1837 // and reset async cache information to allow a second async execute
1838 if (null != _cachedAsyncState) {
1839 _cachedAsyncState.ResetAsyncState();
1841 ReliablePutStateObject();
1842 completion.TrySetException(e);
1846 public XmlReader EndExecuteXmlReader(IAsyncResult asyncResult) {
1848 return EndExecuteXmlReaderInternal(asyncResult);
1851 Bid.CorrelationTrace("<sc.SqlCommand.EndExecuteXmlReader|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1855 private XmlReader EndExecuteXmlReaderAsync(IAsyncResult asyncResult) {
1856 Bid.CorrelationTrace("<sc.SqlCommand.EndExecuteXmlReaderAsync|Info|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1857 Debug.Assert(!_internalEndExecuteInitiated || _stateObj == null);
1859 Exception asyncException = ((Task)asyncResult).Exception;
1860 if (asyncException != null) {
1861 // Leftover exception from the Begin...InternalReadStage
1862 ReliablePutStateObject();
1863 throw asyncException.InnerException;
1866 ThrowIfReconnectionHasBeenCanceled();
1867 // lock on _stateObj prevents ----s with close/cancel.
1868 // If we have already initiate the End call internally, we have already done that, so no point doing it again.
1869 if (!_internalEndExecuteInitiated) {
1871 return EndExecuteXmlReaderInternal(asyncResult);
1875 return EndExecuteXmlReaderInternal(asyncResult);
1880 private XmlReader EndExecuteXmlReaderInternal(IAsyncResult asyncResult) {
1881 bool success = false;
1882 int? sqlExceptionNumber = null;
1884 XmlReader result = CompleteXmlReader(InternalEndExecuteReader(asyncResult, ADP.EndExecuteXmlReader, isInternal: false));
1888 catch (SqlException e){
1889 sqlExceptionNumber = e.Number;
1890 if (cachedAsyncState != null) {
1891 cachedAsyncState.ResetAsyncState();
1894 // SqlException is always catchable
1895 ReliablePutStateObject();
1898 catch (Exception e) {
1899 if (cachedAsyncState != null) {
1900 cachedAsyncState.ResetAsyncState();
1902 if (ADP.IsCatchableExceptionType(e)) {
1903 ReliablePutStateObject();
1908 WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : false);
1912 private XmlReader CompleteXmlReader(SqlDataReader ds) {
1913 XmlReader xr = null;
1915 SmiExtendedMetaData[] md = ds.GetInternalSmiMetaData();
1916 bool isXmlCapable = (null != md && md.Length == 1 && (md[0].SqlDbType == SqlDbType.NText
1917 || md[0].SqlDbType == SqlDbType.NVarChar
1918 || md[0].SqlDbType == SqlDbType.Xml));
1922 SqlStream sqlBuf = new SqlStream(ds, true /*addByteOrderMark*/, (md[0].SqlDbType == SqlDbType.Xml) ? false : true /*process all rows*/);
1923 xr = sqlBuf.ToXmlReader();
1925 catch (Exception e) {
1926 if (ADP.IsCatchableExceptionType(e)) {
1934 throw SQL.NonXmlResult();
1939 [System.Security.Permissions.HostProtectionAttribute(ExternalThreading=true)]
1940 public IAsyncResult BeginExecuteReader() {
1941 return BeginExecuteReader(null, null, CommandBehavior.Default);
1944 [System.Security.Permissions.HostProtectionAttribute(ExternalThreading=true)]
1945 public IAsyncResult BeginExecuteReader(AsyncCallback callback, object stateObject) {
1946 return BeginExecuteReader(callback, stateObject, CommandBehavior.Default);
1949 override protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior) {
1950 Bid.CorrelationTrace("<sc.SqlCommand.ExecuteDbDataReader|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1951 return ExecuteReader(behavior, ADP.ExecuteReader);
1954 new public SqlDataReader ExecuteReader() {
1955 SqlStatistics statistics = null;
1957 Bid.ScopeEnter(out hscp, "<sc.SqlCommand.ExecuteReader|API> %d#", ObjectID);
1958 Bid.CorrelationTrace("<sc.SqlCommand.ExecuteReader|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
1960 statistics = SqlStatistics.StartTimer(Statistics);
1961 return ExecuteReader(CommandBehavior.Default, ADP.ExecuteReader);
1964 SqlStatistics.StopTimer(statistics);
1965 Bid.ScopeLeave(ref hscp);
1969 new public SqlDataReader ExecuteReader(CommandBehavior behavior) {
1971 Bid.ScopeEnter(out hscp, "<sc.SqlCommand.ExecuteReader|API> %d#, behavior=%d{ds.CommandBehavior}", ObjectID, (int)behavior);
1972 Bid.CorrelationTrace("<sc.SqlCommand.ExecuteReader|API|Correlation> ObjectID%d#, behavior=%d{ds.CommandBehavior}, ActivityID %ls\n", ObjectID, (int)behavior);
1974 return ExecuteReader(behavior, ADP.ExecuteReader);
1977 Bid.ScopeLeave(ref hscp);
1981 [System.Security.Permissions.HostProtectionAttribute(ExternalThreading=true)]
1982 public IAsyncResult BeginExecuteReader(CommandBehavior behavior) {
1983 return BeginExecuteReader(null, null, behavior);
1986 [System.Security.Permissions.HostProtectionAttribute(ExternalThreading=true)]
1987 public IAsyncResult BeginExecuteReader(AsyncCallback callback, object stateObject, CommandBehavior behavior) {
1988 Bid.CorrelationTrace("<sc.SqlCommand.BeginExecuteReader|API|Correlation> ObjectID%d#, behavior=%d{ds.CommandBehavior}, ActivityID %ls\n", ObjectID, (int)behavior);
1989 SqlConnection.ExecutePermission.Demand();
1990 return BeginExecuteReaderInternal(behavior, callback, stateObject, 0, inRetry: false);
1993 internal SqlDataReader ExecuteReader(CommandBehavior behavior, string method) {
1994 SqlConnection.ExecutePermission.Demand(); //
1996 // Reset _pendingCancel upon entry into any Execute - used to synchronize state
1997 // between entry into Execute* API and the thread obtaining the stateObject.
1998 _pendingCancel = false;
2000 SqlStatistics statistics = null;
2002 TdsParser bestEffortCleanupTarget = null;
2003 RuntimeHelpers.PrepareConstrainedRegions();
2004 bool success = false;
2005 int? sqlExceptionNumber = null;
2007 WriteBeginExecuteEvent();
2009 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
2011 RuntimeHelpers.PrepareConstrainedRegions();
2013 tdsReliabilitySection.Start();
2017 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
2018 statistics = SqlStatistics.StartTimer(Statistics);
2019 SqlDataReader result = RunExecuteReader(behavior, RunBehavior.ReturnImmediately, true, method);
2025 tdsReliabilitySection.Stop();
2029 catch (SqlException e) {
2030 sqlExceptionNumber = e.Number;
2033 catch (System.OutOfMemoryException e) {
2034 _activeConnection.Abort(e);
2037 catch (System.StackOverflowException e) {
2038 _activeConnection.Abort(e);
2041 catch (System.Threading.ThreadAbortException e) {
2042 _activeConnection.Abort(e);
2043 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
2047 SqlStatistics.StopTimer(statistics);
2048 WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : true);
2052 public SqlDataReader EndExecuteReader(IAsyncResult asyncResult) {
2054 return EndExecuteReaderInternal(asyncResult);
2057 Bid.CorrelationTrace("<sc.SqlCommand.EndExecuteReader|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
2061 private SqlDataReader EndExecuteReaderAsync(IAsyncResult asyncResult) {
2062 Bid.CorrelationTrace("<sc.SqlCommand.EndExecuteReaderAsync|Info|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
2063 Debug.Assert(!_internalEndExecuteInitiated || _stateObj == null);
2065 Exception asyncException = ((Task)asyncResult).Exception;
2066 if (asyncException != null) {
2067 // Leftover exception from the Begin...InternalReadStage
2068 ReliablePutStateObject();
2069 throw asyncException.InnerException;
2072 ThrowIfReconnectionHasBeenCanceled();
2073 // lock on _stateObj prevents ----s with close/cancel.
2074 // If we have already initiate the End call internally, we have already done that, so no point doing it again.
2075 if (!_internalEndExecuteInitiated) {
2077 return EndExecuteReaderInternal(asyncResult);
2081 return EndExecuteReaderInternal(asyncResult);
2086 private SqlDataReader EndExecuteReaderInternal(IAsyncResult asyncResult) {
2087 SqlStatistics statistics = null;
2088 bool success = false;
2089 int? sqlExceptionNumber = null;
2091 statistics = SqlStatistics.StartTimer(Statistics);
2092 SqlDataReader result = InternalEndExecuteReader(asyncResult, ADP.EndExecuteReader, isInternal: false);
2096 catch (SqlException e) {
2097 sqlExceptionNumber = e.Number;
2098 if (cachedAsyncState != null)
2100 cachedAsyncState.ResetAsyncState();
2103 // SqlException is always catchable
2104 ReliablePutStateObject();
2107 catch (Exception e) {
2108 if (cachedAsyncState != null) {
2109 cachedAsyncState.ResetAsyncState();
2111 if (ADP.IsCatchableExceptionType(e)) {
2112 ReliablePutStateObject();
2117 SqlStatistics.StopTimer(statistics);
2118 WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : false);
2122 private IAsyncResult BeginExecuteReaderAsync(CommandBehavior behavior, AsyncCallback callback, object stateObject) {
2123 return BeginExecuteReaderInternal(behavior, callback, stateObject, CommandTimeout, inRetry: false, asyncWrite:true);
2126 private IAsyncResult BeginExecuteReaderInternal(CommandBehavior behavior, AsyncCallback callback, object stateObject, int timeout, bool inRetry, bool asyncWrite = false) {
2127 TaskCompletionSource<object> globalCompletion = new TaskCompletionSource<object>(stateObject);
2128 TaskCompletionSource<object> localCompletion = new TaskCompletionSource<object>(stateObject);
2131 // Reset _pendingCancel upon entry into any Execute - used to synchronize state
2132 // between entry into Execute* API and the thread obtaining the stateObject.
2133 _pendingCancel = false;
2136 SqlStatistics statistics = null;
2139 statistics = SqlStatistics.StartTimer(Statistics);
2140 WriteBeginExecuteEvent();
2142 ValidateAsyncCommand(); // Special case - done outside of try/catches to prevent putting a stateObj
2143 // back into pool when we should not.
2147 Task writeTask = null;
2148 try { // InternalExecuteNonQuery already has reliability block, but if failure will not put stateObj back into pool.
2149 RunExecuteReader(behavior, RunBehavior.ReturnImmediately, true, ADP.BeginExecuteReader, localCompletion, timeout, out writeTask, out usedCache, asyncWrite, inRetry);
2151 catch (Exception e) {
2152 if (!ADP.IsCatchableOrSecurityExceptionType(e)) {
2153 // If not catchable - the connection has already been caught and doomed in RunExecuteReader.
2157 // For async, RunExecuteReader will never put the stateObj back into the pool, so do so now.
2158 ReliablePutStateObject();
2162 if (writeTask != null ) {
2163 AsyncHelper.ContinueTask(writeTask, localCompletion, () => BeginExecuteReaderInternalReadStage(localCompletion));
2166 BeginExecuteReaderInternalReadStage(localCompletion);
2169 // When we use query caching for parameter encryption we need to retry on specific errors.
2170 // In these cases finalize the call internally and trigger a retry when needed.
2171 if (!TriggerInternalEndAndRetryIfNecessary(behavior, stateObject, timeout, ADP.EndExecuteReader, usedCache, inRetry, asyncWrite, globalCompletion, localCompletion, InternalEndExecuteReader, BeginExecuteReaderInternal)) {
2172 globalCompletion = localCompletion;
2175 // Add callback after work is done to avoid overlapping Begin\End methods
2176 if (callback != null) {
2177 globalCompletion.Task.ContinueWith((t) => callback(t), TaskScheduler.Default);
2180 return globalCompletion.Task;
2183 SqlStatistics.StopTimer(statistics);
2187 private bool TriggerInternalEndAndRetryIfNecessary(CommandBehavior behavior, object stateObject, int timeout, string endMethod, bool usedCache, bool inRetry, bool asyncWrite, TaskCompletionSource<object> globalCompletion, TaskCompletionSource<object> localCompletion, Func<IAsyncResult, string, bool, object> endFunc, Func<CommandBehavior, AsyncCallback, object, int, bool, bool, IAsyncResult> retryFunc) {
2188 // We shouldn't be using the cache if we are in retry.
2189 Debug.Assert(!usedCache || !inRetry);
2191 // If column ecnryption is enabled and we used the cache, we want to catch any potential exceptions that were caused by the query cache and retry if the error indicates that we should.
2192 // So, try to read the result of the query before completing the overall task and trigger a retry if appropriate.
2193 if ((IsColumnEncryptionEnabled && !inRetry && usedCache)
2195 || _forceInternalEndQuery
2198 long firstAttemptStart = ADP.TimerCurrent();
2200 localCompletion.Task.ContinueWith(tsk => {
2201 if (tsk.IsFaulted) {
2202 globalCompletion.TrySetException(tsk.Exception.InnerException);
2204 else if (tsk.IsCanceled) {
2205 globalCompletion.TrySetCanceled();
2209 // Mark that we initiated the internal EndExecute. This should always be false until we set it here.
2210 Debug.Assert(!_internalEndExecuteInitiated);
2211 _internalEndExecuteInitiated = true;
2213 // lock on _stateObj prevents ----s with close/cancel.
2215 endFunc(tsk, endMethod, true/*inInternal*/);
2217 globalCompletion.TrySetResult(tsk.Result);
2219 catch (Exception e) {
2220 // Put the state object back to the cache.
2221 // Do not reset the async state, since this is managed by the user Begin/End and not internally.
2222 if (ADP.IsCatchableExceptionType(e)) {
2223 ReliablePutStateObject();
2226 bool shouldRetry = false;
2228 // Check if we have an error indicating that we can retry.
2229 if (e is SqlException) {
2230 SqlException sqlEx = e as SqlException;
2232 for (int i = 0; i < sqlEx.Errors.Count; i++) {
2233 if (sqlEx.Errors[i].Number == TdsEnums.TCE_CONVERSION_ERROR_CLIENT_RETRY) {
2241 // If we cannot retry, Reset the async state to make sure we leave a clean state.
2242 if (null != _cachedAsyncState) {
2243 _cachedAsyncState.ResetAsyncState();
2245 _activeConnection.GetOpenTdsConnection().DecrementAsyncCount();
2247 globalCompletion.TrySetException(e);
2250 // Remove the enrty from the cache since it was inconsistent.
2251 SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this);
2254 // Kick off the retry.
2255 _internalEndExecuteInitiated = false;
2256 Task<object> retryTask = (Task<object>)retryFunc(behavior, null, stateObject, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), true/*inRetry*/, asyncWrite);
2258 retryTask.ContinueWith(retryTsk => {
2259 if (retryTsk.IsFaulted) {
2260 globalCompletion.TrySetException(retryTsk.Exception.InnerException);
2262 else if (retryTsk.IsCanceled) {
2263 globalCompletion.TrySetCanceled();
2266 globalCompletion.TrySetResult(retryTsk.Result);
2268 }, TaskScheduler.Default);
2270 catch (Exception e2) {
2271 globalCompletion.TrySetException(e2);
2276 }, TaskScheduler.Default);
2285 private void BeginExecuteReaderInternalReadStage(TaskCompletionSource<object> completion) {
2286 Debug.Assert(completion != null,"CompletionSource should not be null");
2287 // Read SNI does not have catches for async exceptions, handle here.
2288 TdsParser bestEffortCleanupTarget = null;
2289 RuntimeHelpers.PrepareConstrainedRegions();
2292 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
2294 RuntimeHelpers.PrepareConstrainedRegions();
2296 tdsReliabilitySection.Start();
2300 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
2301 // must finish caching information before ReadSni which can activate the callback before returning
2302 cachedAsyncState.SetActiveConnectionAndResult(completion, ADP.EndExecuteReader, _activeConnection);
2303 _stateObj.ReadSni(completion);
2307 tdsReliabilitySection.Stop();
2311 catch (System.OutOfMemoryException e) {
2312 _activeConnection.Abort(e);
2313 completion.TrySetException(e);
2316 catch (System.StackOverflowException e) {
2317 _activeConnection.Abort(e);
2318 completion.TrySetException(e);
2321 catch (System.Threading.ThreadAbortException e) {
2322 _activeConnection.Abort(e);
2323 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
2324 completion.TrySetException(e);
2327 catch (Exception e) {
2328 // Similarly, if an exception occurs put the stateObj back into the pool.
2329 // and reset async cache information to allow a second async execute
2330 if (null != _cachedAsyncState) {
2331 _cachedAsyncState.ResetAsyncState();
2333 ReliablePutStateObject();
2334 completion.TrySetException(e);
2338 private SqlDataReader InternalEndExecuteReader(IAsyncResult asyncResult, string endMethod, bool isInternal) {
2340 VerifyEndExecuteState((Task)asyncResult, endMethod);
2341 WaitForAsyncResults(asyncResult, isInternal);
2343 // If column encryption is enabled, also check the state after waiting for the task.
2344 // It would be better to do this for all cases, but avoiding for compatibility reasons.
2345 if (IsColumnEncryptionEnabled) {
2346 VerifyEndExecuteState((Task)asyncResult, endMethod, fullCheckForColumnEncryption: true);
2349 CheckThrowSNIException();
2351 TdsParser bestEffortCleanupTarget = null;
2352 RuntimeHelpers.PrepareConstrainedRegions();
2355 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
2357 RuntimeHelpers.PrepareConstrainedRegions();
2359 tdsReliabilitySection.Start();
2363 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
2364 SqlDataReader reader = CompleteAsyncExecuteReader(isInternal);
2365 Debug.Assert(null == _stateObj, "non-null state object in InternalEndExecuteReader");
2370 tdsReliabilitySection.Stop();
2374 catch (System.OutOfMemoryException e) {
2375 _activeConnection.Abort(e);
2378 catch (System.StackOverflowException e) {
2379 _activeConnection.Abort(e);
2382 catch (System.Threading.ThreadAbortException e) {
2383 _activeConnection.Abort(e);
2384 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
2389 public override Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken) {
2391 Bid.CorrelationTrace("<sc.SqlCommand.ExecuteNonQueryAsync|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
2392 SqlConnection.ExecutePermission.Demand();
2394 TaskCompletionSource<int> source = new TaskCompletionSource<int>();
2396 CancellationTokenRegistration registration = new CancellationTokenRegistration();
2397 if (cancellationToken.CanBeCanceled) {
2398 if (cancellationToken.IsCancellationRequested) {
2399 source.SetCanceled();
2402 registration = cancellationToken.Register(CancelIgnoreFailure);
2405 Task<int> returnedTask = source.Task;
2407 RegisterForConnectionCloseNotification(ref returnedTask);
2409 Task<int>.Factory.FromAsync(BeginExecuteNonQueryAsync, EndExecuteNonQueryAsync, null).ContinueWith((t) => {
2410 registration.Dispose();
2412 Exception e = t.Exception.InnerException;
2413 source.SetException(e);
2417 source.SetCanceled();
2420 source.SetResult(t.Result);
2423 }, TaskScheduler.Default);
2425 catch (Exception e) {
2426 source.SetException(e);
2429 return returnedTask;
2432 protected override Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) {
2433 return ExecuteReaderAsync(behavior, cancellationToken).ContinueWith<DbDataReader>((result) => {
2434 if (result.IsFaulted) {
2435 throw result.Exception.InnerException;
2437 return result.Result;
2438 }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default);
2441 new public Task<SqlDataReader> ExecuteReaderAsync() {
2442 return ExecuteReaderAsync(CommandBehavior.Default, CancellationToken.None);
2445 new public Task<SqlDataReader> ExecuteReaderAsync(CommandBehavior behavior) {
2446 return ExecuteReaderAsync(behavior, CancellationToken.None);
2449 new public Task<SqlDataReader> ExecuteReaderAsync(CancellationToken cancellationToken) {
2450 return ExecuteReaderAsync(CommandBehavior.Default, cancellationToken);
2453 new public Task<SqlDataReader> ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) {
2455 Bid.CorrelationTrace("<sc.SqlCommand.ExecuteReaderAsync|API|Correlation> ObjectID%d#, behavior=%d{ds.CommandBehavior}, ActivityID %ls\n", ObjectID, (int)behavior);
2456 SqlConnection.ExecutePermission.Demand();
2458 TaskCompletionSource<SqlDataReader> source = new TaskCompletionSource<SqlDataReader>();
2460 CancellationTokenRegistration registration = new CancellationTokenRegistration();
2461 if (cancellationToken.CanBeCanceled) {
2462 if (cancellationToken.IsCancellationRequested) {
2463 source.SetCanceled();
2466 registration = cancellationToken.Register(CancelIgnoreFailure);
2469 Task<SqlDataReader> returnedTask = source.Task;
2471 RegisterForConnectionCloseNotification(ref returnedTask);
2473 Task<SqlDataReader>.Factory.FromAsync(BeginExecuteReaderAsync, EndExecuteReaderAsync, behavior, null).ContinueWith((t) => {
2474 registration.Dispose();
2476 Exception e = t.Exception.InnerException;
2477 source.SetException(e);
2481 source.SetCanceled();
2484 source.SetResult(t.Result);
2487 }, TaskScheduler.Default);
2489 catch (Exception e) {
2490 source.SetException(e);
2493 return returnedTask;
2496 public override Task<object> ExecuteScalarAsync(CancellationToken cancellationToken)
2498 return ExecuteReaderAsync(cancellationToken).ContinueWith((executeTask) => {
2499 TaskCompletionSource<object> source = new TaskCompletionSource<object>();
2500 if (executeTask.IsCanceled) {
2501 source.SetCanceled();
2503 else if (executeTask.IsFaulted) {
2504 source.SetException(executeTask.Exception.InnerException);
2507 SqlDataReader reader = executeTask.Result;
2508 reader.ReadAsync(cancellationToken).ContinueWith((readTask) => {
2510 if (readTask.IsCanceled) {
2512 source.SetCanceled();
2514 else if (readTask.IsFaulted) {
2516 source.SetException(readTask.Exception.InnerException);
2519 Exception exception = null;
2520 object result = null;
2522 bool more = readTask.Result;
2523 if (more && reader.FieldCount > 0) {
2525 result = reader.GetValue(0);
2527 catch (Exception e) {
2535 if (exception != null) {
2536 source.SetException(exception);
2539 source.SetResult(result);
2543 catch (Exception e) {
2544 // exception thrown by Dispose...
2545 source.SetException(e);
2547 }, TaskScheduler.Default);
2550 }, TaskScheduler.Default).Unwrap();
2553 public Task<XmlReader> ExecuteXmlReaderAsync() {
2554 return ExecuteXmlReaderAsync(CancellationToken.None);
2557 public Task<XmlReader> ExecuteXmlReaderAsync(CancellationToken cancellationToken) {
2559 Bid.CorrelationTrace("<sc.SqlCommand.ExecuteXmlReaderAsync|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
2560 SqlConnection.ExecutePermission.Demand();
2562 TaskCompletionSource<XmlReader> source = new TaskCompletionSource<XmlReader>();
2564 CancellationTokenRegistration registration = new CancellationTokenRegistration();
2565 if (cancellationToken.CanBeCanceled) {
2566 if (cancellationToken.IsCancellationRequested) {
2567 source.SetCanceled();
2570 registration = cancellationToken.Register(CancelIgnoreFailure);
2573 Task<XmlReader> returnedTask = source.Task;
2575 RegisterForConnectionCloseNotification(ref returnedTask);
2577 Task<XmlReader>.Factory.FromAsync(BeginExecuteXmlReaderAsync, EndExecuteXmlReaderAsync, null).ContinueWith((t) => {
2578 registration.Dispose();
2580 Exception e = t.Exception.InnerException;
2581 source.SetException(e);
2585 source.SetCanceled();
2588 source.SetResult(t.Result);
2591 }, TaskScheduler.Default);
2593 catch (Exception e) {
2594 source.SetException(e);
2597 return returnedTask;
2600 // If the user part is quoted, remove first and last brackets and then unquote any right square
2601 // brackets in the procedure. This is a very simple parser that performs no validation. As
2602 // with the function below, ideally we should have support from the server for this.
2603 private static string UnquoteProcedurePart(string part) {
2604 if ((null != part) && (2 <= part.Length)) {
2605 if ('[' == part[0] && ']' == part[part.Length-1]) {
2606 part = part.Substring(1, part.Length-2); // strip outer '[' & ']'
2607 part = part.Replace("]]", "]"); // undo quoted "]" from "]]" to "]"
2613 // User value in this format: [server].[database].[schema].[sp_foo];1
2614 // This function should only be passed "[sp_foo];1".
2615 // This function uses a pretty simple parser that doesn't do any validation.
2616 // Ideally, we would have support from the server rather than us having to do this.
2617 private static string UnquoteProcedureName(string name, out object groupNumber) {
2618 groupNumber = null; // Out param - initialize value to no value.
2619 string sproc = name;
2621 if (null != sproc) {
2622 if (Char.IsDigit(sproc[sproc.Length-1])) { // If last char is a digit, parse.
2623 int semicolon = sproc.LastIndexOf(';');
2624 if (semicolon != -1) { // If we found a semicolon, obtain the integer.
2625 string part = sproc.Substring(semicolon+1);
2627 if (Int32.TryParse(part, out number)) { // No checking, just fail if this doesn't work.
2628 groupNumber = number;
2629 sproc = sproc.Substring(0, semicolon);
2633 sproc = UnquoteProcedurePart(sproc);
2638 //index into indirection arrays for columns of interest to DeriveParameters
2639 private enum ProcParamsColIndex {
2642 DataType, // obsolete in katmai, use ManagedDataType instead
2643 ManagedDataType, // new in katmai
2644 CharacterMaximumLength,
2650 XmlSchemaCollectionCatalogName,
2651 XmlSchemaCollectionSchemaName,
2652 XmlSchemaCollectionName,
2653 UdtTypeName, // obsolete in Katmai. Holds the actual typename if UDT, since TypeName didn't back then.
2654 DateTimeScale // new in Katmai
2657 // Yukon- column ordinals (this array indexed by ProcParamsColIndex
2658 static readonly internal string[] PreKatmaiProcParamsNames = new string[] {
2659 "PARAMETER_NAME", // ParameterName,
2660 "PARAMETER_TYPE", // ParameterType,
2661 "DATA_TYPE", // DataType
2662 null, // ManagedDataType, introduced in Katmai
2663 "CHARACTER_MAXIMUM_LENGTH", // CharacterMaximumLength,
2664 "NUMERIC_PRECISION", // NumericPrecision,
2665 "NUMERIC_SCALE", // NumericScale,
2666 "UDT_CATALOG", // TypeCatalogName,
2667 "UDT_SCHEMA", // TypeSchemaName,
2668 "TYPE_NAME", // TypeName,
2669 "XML_CATALOGNAME", // XmlSchemaCollectionCatalogName,
2670 "XML_SCHEMANAME", // XmlSchemaCollectionSchemaName,
2671 "XML_SCHEMACOLLECTIONNAME", // XmlSchemaCollectionName
2672 "UDT_NAME", // UdtTypeName
2673 null, // Scale for datetime types with scale, introduced in Katmai
2676 // Katmai+ column ordinals (this array indexed by ProcParamsColIndex
2677 static readonly internal string[] KatmaiProcParamsNames = new string[] {
2678 "PARAMETER_NAME", // ParameterName,
2679 "PARAMETER_TYPE", // ParameterType,
2680 null, // DataType, removed from Katmai+
2681 "MANAGED_DATA_TYPE", // ManagedDataType,
2682 "CHARACTER_MAXIMUM_LENGTH", // CharacterMaximumLength,
2683 "NUMERIC_PRECISION", // NumericPrecision,
2684 "NUMERIC_SCALE", // NumericScale,
2685 "TYPE_CATALOG_NAME", // TypeCatalogName,
2686 "TYPE_SCHEMA_NAME", // TypeSchemaName,
2687 "TYPE_NAME", // TypeName,
2688 "XML_CATALOGNAME", // XmlSchemaCollectionCatalogName,
2689 "XML_SCHEMANAME", // XmlSchemaCollectionSchemaName,
2690 "XML_SCHEMACOLLECTIONNAME", // XmlSchemaCollectionName
2691 null, // UdtTypeName, removed from Katmai+
2692 "SS_DATETIME_PRECISION", // Scale for datetime types with scale
2696 internal void DeriveParameters() {
2697 switch (this.CommandType) {
2698 case System.Data.CommandType.Text:
2699 throw ADP.DeriveParametersNotSupported(this);
2700 case System.Data.CommandType.StoredProcedure:
2702 case System.Data.CommandType.TableDirect:
2703 // CommandType.TableDirect - do nothing, parameters are not supported
2704 throw ADP.DeriveParametersNotSupported(this);
2706 throw ADP.InvalidCommandType(this.CommandType);
2709 // validate that we have a valid connection
2710 ValidateCommand(ADP.DeriveParameters, false /*not async*/);
2712 // Use common parser for SqlClient and OleDb - parse into 4 parts - Server, Catalog, Schema, ProcedureName
2713 string[] parsedSProc = MultipartIdentifier.ParseMultipartIdentifier(this.CommandText, "[\"", "]\"", Res.SQL_SqlCommandCommandText, false);
2714 if (null == parsedSProc[3] || ADP.IsEmpty(parsedSProc[3]))
2716 throw ADP.NoStoredProcedureExists(this.CommandText);
2719 Debug.Assert(parsedSProc.Length == 4, "Invalid array length result from SqlCommandBuilder.ParseProcedureName");
2721 SqlCommand paramsCmd = null;
2722 StringBuilder cmdText = new StringBuilder();
2724 // Build call for sp_procedure_params_rowset built of unquoted values from user:
2725 // [user server, if provided].[user catalog, else current database].[sys if Yukon, else blank].[sp_procedure_params_rowset]
2727 // Server - pass only if user provided.
2728 if (!ADP.IsEmpty(parsedSProc[0])) {
2729 SqlCommandSet.BuildStoredProcedureName(cmdText, parsedSProc[0]);
2730 cmdText.Append(".");
2733 // Catalog - pass user provided, otherwise use current database.
2734 if (ADP.IsEmpty(parsedSProc[1])) {
2735 parsedSProc[1] = this.Connection.Database;
2737 SqlCommandSet.BuildStoredProcedureName(cmdText, parsedSProc[1]);
2738 cmdText.Append(".");
2740 // Schema - only if Yukon, and then only pass sys. Also - pass managed version of sproc
2741 // for Yukon, else older sproc.
2743 bool useManagedDataType;
2744 if (this.Connection.IsKatmaiOrNewer) {
2745 // Procedure - [sp_procedure_params_managed]
2746 cmdText.Append("[sys].[").Append(TdsEnums.SP_PARAMS_MGD10).Append("]");
2748 colNames = KatmaiProcParamsNames;
2749 useManagedDataType = true;
2752 if (this.Connection.IsYukonOrNewer) {
2753 // Procedure - [sp_procedure_params_managed]
2754 cmdText.Append("[sys].[").Append(TdsEnums.SP_PARAMS_MANAGED).Append("]");
2757 // Procedure - [sp_procedure_params_rowset]
2758 cmdText.Append(".[").Append(TdsEnums.SP_PARAMS).Append("]");
2761 colNames = PreKatmaiProcParamsNames;
2762 useManagedDataType = false;
2766 paramsCmd = new SqlCommand(cmdText.ToString(), this.Connection, this.Transaction);
2767 paramsCmd.CommandType = CommandType.StoredProcedure;
2771 // Prepare parameters for sp_procedure_params_rowset:
2772 // 1) procedure name - unquote user value
2773 // 2) group number - parsed at the time we unquoted procedure name
2774 // 3) procedure schema - unquote user value
2780 paramsCmd.Parameters.Add(new SqlParameter("@procedure_name", SqlDbType.NVarChar, 255));
2781 paramsCmd.Parameters[0].Value = UnquoteProcedureName(parsedSProc[3], out groupNumber); // ProcedureName is 4rd element in parsed array
2783 if (null != groupNumber) {
2784 SqlParameter param = paramsCmd.Parameters.Add(new SqlParameter("@group_number", SqlDbType.Int));
2785 param.Value = groupNumber;
2788 if (!ADP.IsEmpty(parsedSProc[2])) { // SchemaName is 3rd element in parsed array
2789 SqlParameter param = paramsCmd.Parameters.Add(new SqlParameter("@procedure_schema", SqlDbType.NVarChar, 255));
2790 param.Value = UnquoteProcedurePart(parsedSProc[2]);
2793 SqlDataReader r = null;
2795 List<SqlParameter> parameters = new List<SqlParameter>();
2796 bool processFinallyBlock = true;
2799 r = paramsCmd.ExecuteReader();
2801 SqlParameter p = null;
2804 // each row corresponds to a parameter of the stored proc. Fill in all the info
2806 p = new SqlParameter();
2809 p.ParameterName = (string) r[colNames[(int)ProcParamsColIndex.ParameterName]];
2812 if (useManagedDataType) {
2813 p.SqlDbType = (SqlDbType)(short)r[colNames[(int)ProcParamsColIndex.ManagedDataType]];
2815 // Yukon didn't have as accurate of information as we're getting for Katmai, so re-map a couple of
2816 // types for backward compatability.
2817 switch (p.SqlDbType) {
2818 case SqlDbType.Image:
2819 case SqlDbType.Timestamp:
2820 p.SqlDbType = SqlDbType.VarBinary;
2823 case SqlDbType.NText:
2824 p.SqlDbType = SqlDbType.NVarChar;
2827 case SqlDbType.Text:
2828 p.SqlDbType = SqlDbType.VarChar;
2836 p.SqlDbType = MetaType.GetSqlDbTypeFromOleDbType((short)r[colNames[(int)ProcParamsColIndex.DataType]],
2837 ADP.IsNull(r[colNames[(int)ProcParamsColIndex.TypeName]]) ?
2839 (string)r[colNames[(int)ProcParamsColIndex.TypeName]]);
2843 object a = r[colNames[(int)ProcParamsColIndex.CharacterMaximumLength]];
2847 // Map MAX sizes correctly. The Katmai server-side proc sends 0 for these instead of -1.
2848 // Should be fixed on the Katmai side, but would likely hold up the RI, and is safer to fix here.
2849 // If we can get the server-side fixed before shipping Katmai, we can remove this mapping.
2851 (p.SqlDbType == SqlDbType.NVarChar ||
2852 p.SqlDbType == SqlDbType.VarBinary ||
2853 p.SqlDbType == SqlDbType.VarChar)) {
2860 p.Direction = ParameterDirectionFromOleDbDirection((short)r[colNames[(int)ProcParamsColIndex.ParameterType]]);
2862 if (p.SqlDbType == SqlDbType.Decimal) {
2863 p.ScaleInternal = (byte) ((short)r[colNames[(int)ProcParamsColIndex.NumericScale]] & 0xff);
2864 p.PrecisionInternal = (byte)((short)r[colNames[(int)ProcParamsColIndex.NumericPrecision]] & 0xff);
2867 // type name for Udt
2868 if (SqlDbType.Udt == p.SqlDbType) {
2870 Debug.Assert(this._activeConnection.IsYukonOrNewer,"Invalid datatype token received from pre-yukon server");
2873 if (useManagedDataType) {
2874 udtTypeName = (string)r[colNames[(int)ProcParamsColIndex.TypeName]];
2877 udtTypeName = (string)r[colNames[(int)ProcParamsColIndex.UdtTypeName]];
2880 //read the type name
2881 p.UdtTypeName = r[colNames[(int)ProcParamsColIndex.TypeCatalogName]]+"."+
2882 r[colNames[(int)ProcParamsColIndex.TypeSchemaName]]+"."+
2886 // type name for Structured types (same as for Udt's except assign p.TypeName instead of p.UdtTypeName
2887 if (SqlDbType.Structured == p.SqlDbType) {
2889 Debug.Assert(this._activeConnection.IsKatmaiOrNewer,"Invalid datatype token received from pre-katmai server");
2891 //read the type name
2892 p.TypeName = r[colNames[(int)ProcParamsColIndex.TypeCatalogName]]+"."+
2893 r[colNames[(int)ProcParamsColIndex.TypeSchemaName]]+"."+
2894 r[colNames[(int)ProcParamsColIndex.TypeName]];
2897 // XmlSchema name for Xml types
2898 if (SqlDbType.Xml == p.SqlDbType) {
2901 value = r[colNames[(int)ProcParamsColIndex.XmlSchemaCollectionCatalogName]];
2902 p.XmlSchemaCollectionDatabase = ADP.IsNull(value) ? String.Empty : (string) value;
2904 value = r[colNames[(int)ProcParamsColIndex.XmlSchemaCollectionSchemaName]];
2905 p.XmlSchemaCollectionOwningSchema = ADP.IsNull(value) ? String.Empty : (string) value;
2907 value = r[colNames[(int)ProcParamsColIndex.XmlSchemaCollectionName]];
2908 p.XmlSchemaCollectionName = ADP.IsNull(value) ? String.Empty : (string) value;
2911 if (MetaType._IsVarTime(p.SqlDbType)) {
2912 object value = r[colNames[(int)ProcParamsColIndex.DateTimeScale]];
2914 p.ScaleInternal = (byte)(((int)value) & 0xff);
2921 catch (Exception e) {
2922 processFinallyBlock = ADP.IsCatchableExceptionType(e);
2926 TdsParser.ReliabilitySection.Assert("unreliable call to DeriveParameters"); // you need to setup for a thread abort somewhere before you call this method
2927 if (processFinallyBlock) {
2931 // always unhook the user's connection
2932 paramsCmd.Connection = null;
2936 if (parameters.Count == 0) {
2937 throw ADP.NoStoredProcedureExists(this.CommandText);
2940 this.Parameters.Clear();
2942 foreach (SqlParameter temp in parameters) {
2943 this._parameters.Add(temp);
2947 private ParameterDirection ParameterDirectionFromOleDbDirection(short oledbDirection) {
2948 Debug.Assert(oledbDirection >= 1 && oledbDirection <= 4, "invalid parameter direction from params_rowset!");
2950 switch (oledbDirection) {
2952 return ParameterDirection.InputOutput;
2954 return ParameterDirection.Output;
2956 return ParameterDirection.ReturnValue;
2958 return ParameterDirection.Input;
2963 // get cached metadata
2964 internal _SqlMetaDataSet MetaData {
2966 return _cachedMetaData;
2970 // Check to see if notificactions auto enlistment is turned on. Enlist if so.
2971 private void CheckNotificationStateAndAutoEnlist() {
2972 // First, if auto-enlist is on, check server version and then obtain context if
2973 // present. If so, auto enlist to the dependency ID given in the context data.
2974 if (NotificationAutoEnlist) {
2975 if (_activeConnection.IsYukonOrNewer) { // Only supported for Yukon...
2976 string notifyContext = SqlNotificationContext();
2977 if (!ADP.IsEmpty(notifyContext)) {
2978 // Map to dependency by ID set in context data.
2979 SqlDependency dependency = SqlDependencyPerAppDomainDispatcher.SingletonInstance.LookupDependencyEntry(notifyContext);
2981 if (null != dependency) {
2982 // Add this command to the dependency.
2983 dependency.AddCommandDependency(this);
2989 // If we have a notification with a dependency, setup the notification options at this time.
2991 // If user passes options, then we will always have option data at the time the SqlDependency
2992 // ctor is called. But, if we are using default queue, then we do not have this data until
2993 // Start(). Due to this, we always delay setting options until execute.
2995 // There is a variance in order between Start(), SqlDependency(), and Execute. This is the
2996 // best way to solve that problem.
2997 if (null != Notification) {
2998 if (_sqlDep != null) {
2999 if (null == _sqlDep.Options) {
3000 // If null, SqlDependency was not created with options, so we need to obtain default options now.
3001 // GetDefaultOptions can and will throw under certain conditions.
3003 // In order to match to the appropriate start - we need 3 pieces of info:
3004 // 1) server 2) user identity (SQL Auth or Int Sec) 3) database
3006 SqlDependency.IdentityUserNamePair identityUserName = null;
3008 // Obtain identity from connection.
3009 SqlInternalConnectionTds internalConnection = _activeConnection.InnerConnection as SqlInternalConnectionTds;
3010 if (internalConnection.Identity != null) {
3011 identityUserName = new SqlDependency.IdentityUserNamePair(internalConnection.Identity, null);
3014 identityUserName = new SqlDependency.IdentityUserNamePair(null, internalConnection.ConnectionOptions.UserID);
3017 Notification.Options = SqlDependency.GetDefaultComposedOptions(_activeConnection.DataSource,
3018 InternalTdsConnection.ServerProvidedFailOverPartner,
3019 identityUserName, _activeConnection.Database);
3022 // Set UserData on notifications, as well as adding to the appdomain dispatcher. The value is
3023 // computed by an algorithm on the dependency - fixed and will always produce the same value
3024 // given identical commandtext + parameter values.
3025 Notification.UserData = _sqlDep.ComputeHashAndAddToDispatcher(this);
3026 // Maintain server list for SqlDependency.
3027 _sqlDep.AddToServerList(_activeConnection.DataSource);
3032 [System.Security.Permissions.SecurityPermission(SecurityAction.Assert, Infrastructure=true)]
3033 static internal string SqlNotificationContext() {
3034 SqlConnection.VerifyExecutePermission();
3036 // since this information is protected, follow it so that it is not exposed to the user.
3037 // SQLBU 329633, SQLBU 329637
3038 return (System.Runtime.Remoting.Messaging.CallContext.GetData("MS.SqlDependencyCookie") as string);
3041 // Tds-specific logic for ExecuteNonQuery run handling
3042 private Task RunExecuteNonQueryTds(string methodName, bool async, int timeout, bool asyncWrite ) {
3043 Debug.Assert(!asyncWrite || async, "AsyncWrite should be always accompanied by Async");
3044 bool processFinallyBlock = true;
3047 Task reconnectTask = _activeConnection.ValidateAndReconnect(null, timeout);
3049 if (reconnectTask != null) {
3050 long reconnectionStart = ADP.TimerCurrent();
3052 TaskCompletionSource<object> completion = new TaskCompletionSource<object>();
3053 _activeConnection.RegisterWaitingForReconnect(completion.Task);
3054 _reconnectionCompletionSource = completion;
3055 CancellationTokenSource timeoutCTS = new CancellationTokenSource();
3056 AsyncHelper.SetTimeoutException(completion, timeout, SQL.CR_ReconnectTimeout, timeoutCTS.Token);
3057 AsyncHelper.ContinueTask(reconnectTask, completion,
3059 if (completion.Task.IsCompleted) {
3062 Interlocked.CompareExchange(ref _reconnectionCompletionSource, null, completion);
3063 timeoutCTS.Cancel();
3064 Task subTask = RunExecuteNonQueryTds(methodName, async, TdsParserStaticMethods.GetRemainingTimeout(timeout, reconnectionStart), asyncWrite);
3065 if (subTask == null) {
3066 completion.SetResult(null);
3069 AsyncHelper.ContinueTask(subTask, completion, () => completion.SetResult(null));
3071 }, connectionToAbort: _activeConnection);
3072 return completion.Task;
3075 AsyncHelper.WaitForCompletion(reconnectTask, timeout, () => { throw SQL.CR_ReconnectTimeout(); });
3076 timeout = TdsParserStaticMethods.GetRemainingTimeout(timeout, reconnectionStart);
3081 _activeConnection.AddWeakReference(this, SqlReferenceCollection.CommandTag);
3086 // Reset the encryption state in case it has been set by a previous command.
3087 ResetEncryptionState();
3089 // we just send over the raw text with no annotation
3090 // no parameters are sent over
3091 // no data reader is returned
3092 // use this overload for "batch SQL" tds token type
3093 Bid.Trace("<sc.SqlCommand.ExecuteNonQuery|INFO> %d#, Command executed as SQLBATCH.\n", ObjectID);
3094 Task executeTask = _stateObj.Parser.TdsExecuteSQLBatch(this.CommandText, timeout, this.Notification, _stateObj, sync: true);
3095 Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
3099 _activeConnection.GetOpenTdsConnection(methodName).IncrementAsyncCount();
3103 Debug.Assert(_stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
3104 bool result = _stateObj.Parser.TryRun(RunBehavior.UntilDone, this, null, null, _stateObj, out dataReady);
3105 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
3108 catch (Exception e) {
3109 processFinallyBlock = ADP.IsCatchableExceptionType(e);
3113 TdsParser.ReliabilitySection.Assert("unreliable call to RunExecuteNonQueryTds"); // you need to setup for a thread abort somewhere before you call this method
3114 if (processFinallyBlock && !async) {
3115 // When executing Async, we need to keep the _stateObj alive...
3122 // Smi-specific logic for ExecuteNonQuery
3123 private void RunExecuteNonQuerySmi( bool sendToPipe ) {
3124 SqlInternalConnectionSmi innerConnection = InternalSmiConnection;
3126 // Set it up, process all of the events, and we're done!
3127 SmiRequestExecutor requestExecutor = null;
3129 requestExecutor = SetUpSmiRequest(innerConnection);
3130 SmiExecuteType execType;
3132 execType = SmiExecuteType.ToPipe;
3134 execType = SmiExecuteType.NonQuery;
3137 SmiEventStream eventStream = null;
3138 // Don't need a CER here because caller already has one that will doom the
3139 // connection if it's a finally-skipping type of problem.
3140 bool processFinallyBlock = true;
3143 SysTx.Transaction transaction;
3144 innerConnection.GetCurrentTransactionPair(out transactionId, out transaction);
3146 if (Bid.AdvancedOn) {
3147 Bid.Trace("<sc.SqlCommand.RunExecuteNonQuerySmi|ADV> %d#, innerConnection=%d#, transactionId=0x%I64x, cmdBehavior=%d.\n", ObjectID, innerConnection.ObjectID, transactionId, (int)CommandBehavior.Default);
3150 if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion) {
3151 eventStream = requestExecutor.Execute(
3152 innerConnection.SmiConnection,
3155 CommandBehavior.Default,
3159 eventStream = requestExecutor.Execute(
3160 innerConnection.SmiConnection,
3162 CommandBehavior.Default,
3166 while ( eventStream.HasEvents ) {
3167 eventStream.ProcessEvent( EventSink );
3170 catch (Exception e) {
3171 processFinallyBlock = ADP.IsCatchableExceptionType(e);
3175 TdsParser.ReliabilitySection.Assert("unreliable call to RunExecuteNonQuerySmi"); // you need to setup for a thread abort somewhere before you call this method
3176 if (null != eventStream && processFinallyBlock) {
3177 eventStream.Close( EventSink );
3181 EventSink.ProcessMessagesAndThrow();
3184 if (requestExecutor != null) {
3185 requestExecutor.Close(EventSink);
3186 EventSink.ProcessMessagesAndThrow(ignoreNonFatalMessages: true);
3192 /// Resets the encryption related state of the command object and each of the parameters.
3193 /// BatchRPC doesn't need special handling to cleanup the state of each RPC object and its parameters since a new RPC object and
3194 /// parameters are generated on every execution.
3196 private void ResetEncryptionState() {
3197 // First reset the command level state.
3198 ClearDescribeParameterEncryptionRequests();
3200 // Reset the state for internal End execution.
3201 _internalEndExecuteInitiated = false;
3203 // Reset the state for the cache.
3204 CachingQueryMetadataPostponed = false;
3206 // Reset the state of each of the parameters.
3207 if (_parameters != null) {
3208 for (int i = 0; i < _parameters.Count; i++) {
3209 _parameters[i].CipherMetadata = null;
3210 _parameters[i].HasReceivedMetadata = false;
3216 /// Steps to be executed in the Prepare Transparent Encryption finally block.
3218 private void PrepareTransparentEncryptionFinallyBlock( bool closeDataReader,
3219 bool clearDataStructures,
3220 bool decrementAsyncCount,
3221 bool wasDescribeParameterEncryptionNeeded,
3222 ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap,
3223 SqlDataReader describeParameterEncryptionDataReader) {
3224 if (clearDataStructures) {
3225 // Clear some state variables in SqlCommand that reflect in-progress describe parameter encryption requests.
3226 ClearDescribeParameterEncryptionRequests();
3228 if (describeParameterEncryptionRpcOriginalRpcMap != null) {
3229 describeParameterEncryptionRpcOriginalRpcMap = null;
3233 // Decrement the async count.
3234 if (decrementAsyncCount) {
3235 SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection();
3236 if (internalConnectionTds != null) {
3237 internalConnectionTds.DecrementAsyncCount();
3241 if (closeDataReader) {
3242 // Close the data reader to reset the _stateObj
3243 if (null != describeParameterEncryptionDataReader) {
3244 describeParameterEncryptionDataReader.Close();
3250 /// Executes the reader after checking to see if we need to encrypt input parameters and then encrypting it if required.
3251 /// TryFetchInputParameterEncryptionInfo() -> ReadDescribeEncryptionParameterResults()-> EncryptInputParameters() ->RunExecuteReaderTds()
3253 /// <param name="cmdBehavior"></param>
3254 /// <param name="returnStream"></param>
3255 /// <param name="async"></param>
3256 /// <param name="timeout"></param>
3257 /// <param name="task"></param>
3258 /// <param name="asyncWrite"></param>
3259 /// <returns></returns>
3260 private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool returnStream, bool async, int timeout, TaskCompletionSource<object> completion, out Task returnTask, bool asyncWrite, out bool usedCache, bool inRetry) {
3261 // Fetch reader with input params
3262 Task fetchInputParameterEncryptionInfoTask = null;
3263 bool describeParameterEncryptionNeeded = false;
3264 SqlDataReader describeParameterEncryptionDataReader = null;
3268 Debug.Assert(_activeConnection != null, "_activeConnection should not be null in PrepareForTransparentEncryption.");
3269 Debug.Assert(_activeConnection.Parser != null, "_activeConnection.Parser should not be null in PrepareForTransparentEncryption.");
3270 Debug.Assert(_activeConnection.Parser.IsColumnEncryptionSupported,
3271 "_activeConnection.Parser.IsColumnEncryptionSupported should be true in PrepareForTransparentEncryption.");
3272 Debug.Assert(_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled
3273 || (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && _activeConnection.IsColumnEncryptionSettingEnabled),
3274 "ColumnEncryption setting should be enabled for input parameter encryption.");
3275 Debug.Assert(async == (completion != null), "completion should can be null if and only if mode is async.");
3277 // If we are not in Batch RPC and not already retrying, attempt to fetch the cipher MD for each parameter from the cache.
3278 // If this succeeds then return immediately, otherwise just fall back to the full crypto MD discovery.
3279 if (!BatchRPCMode && !inRetry && SqlQueryMetadataCache.GetInstance().GetQueryMetadataIfExists(this)) {
3284 // A flag to indicate if finallyblock needs to execute.
3285 bool processFinallyBlock = true;
3287 // A flag to indicate if we need to decrement async count on the connection in finally block.
3288 bool decrementAsyncCountInFinallyBlock = false;
3290 // Flag to indicate if exception is caught during the execution, to govern clean up.
3291 bool exceptionCaught = false;
3293 // Used in BatchRPCMode to maintain a map of describe parameter encryption RPC requests (Keys) and their corresponding original RPC requests (Values).
3294 ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap = null;
3296 TdsParser bestEffortCleanupTarget = null;
3297 RuntimeHelpers.PrepareConstrainedRegions();
3300 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
3302 RuntimeHelpers.PrepareConstrainedRegions();
3304 tdsReliabilitySection.Start();
3308 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
3310 // Fetch the encryption information that applies to any of the input parameters.
3311 describeParameterEncryptionDataReader = TryFetchInputParameterEncryptionInfo(timeout,
3314 out describeParameterEncryptionNeeded,
3315 out fetchInputParameterEncryptionInfoTask,
3316 out describeParameterEncryptionRpcOriginalRpcMap);
3318 Debug.Assert(describeParameterEncryptionNeeded || describeParameterEncryptionDataReader == null,
3319 "describeParameterEncryptionDataReader should be null if we don't need to request describe parameter encryption request.");
3321 Debug.Assert(fetchInputParameterEncryptionInfoTask == null || async,
3322 "Task returned by TryFetchInputParameterEncryptionInfo, when in sync mode, in PrepareForTransparentEncryption.");
3324 Debug.Assert((describeParameterEncryptionRpcOriginalRpcMap != null) == BatchRPCMode,
3325 "describeParameterEncryptionRpcOriginalRpcMap can be non-null if and only if it is in BatchRPCMode.");
3327 // If we didn't have parameters, we can fall back to regular code path, by simply returning.
3328 if (!describeParameterEncryptionNeeded) {
3329 Debug.Assert(null == fetchInputParameterEncryptionInfoTask,
3330 "fetchInputParameterEncryptionInfoTask should not be set if describe parameter encryption is not needed.");
3332 Debug.Assert(null == describeParameterEncryptionDataReader,
3333 "SqlDataReader created for describe parameter encryption params when it is not needed.");
3338 // If we are in async execution, we need to decrement our async count on exception.
3339 decrementAsyncCountInFinallyBlock = async;
3341 Debug.Assert(describeParameterEncryptionDataReader != null,
3342 "describeParameterEncryptionDataReader should not be null, as it is required to get results of describe parameter encryption.");
3344 // Fire up another task to read the results of describe parameter encryption
3345 if (fetchInputParameterEncryptionInfoTask != null) {
3346 // Mark that we should not process the finally block since we have async execution pending.
3347 // Note that this should be done outside the task's continuation delegate.
3348 processFinallyBlock = false;
3349 returnTask = AsyncHelper.CreateContinuationTask(fetchInputParameterEncryptionInfoTask, () => {
3350 bool processFinallyBlockAsync = true;
3351 bool decrementAsyncCountInFinallyBlockAsync = true;
3353 RuntimeHelpers.PrepareConstrainedRegions();
3356 TdsParser.ReliabilitySection tdsReliabilitySectionAsync = new TdsParser.ReliabilitySection();
3357 RuntimeHelpers.PrepareConstrainedRegions();
3359 tdsReliabilitySectionAsync.Start();
3361 // Check for any exceptions on network write, before reading.
3362 CheckThrowSNIException();
3364 // If it is async, then TryFetchInputParameterEncryptionInfo-> RunExecuteReaderTds would have incremented the async count.
3365 // Decrement it when we are about to complete async execute reader.
3366 SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection();
3367 if (internalConnectionTds != null) {
3368 internalConnectionTds.DecrementAsyncCount();
3369 decrementAsyncCountInFinallyBlockAsync = false;
3372 // Complete executereader.
3373 describeParameterEncryptionDataReader = CompleteAsyncExecuteReader(forDescribeParameterEncryption: true);
3374 Debug.Assert(null == _stateObj, "non-null state object in PrepareForTransparentEncryption.");
3376 // Read the results of describe parameter encryption.
3377 ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap);
3380 // Failpoint to force the thread to halt to simulate cancellation of SqlCommand.
3381 if (_sleepAfterReadDescribeEncryptionParameterResults) {
3382 Thread.Sleep(10000);
3386 tdsReliabilitySectionAsync.Stop();
3390 catch (Exception e) {
3391 processFinallyBlockAsync = ADP.IsCatchableExceptionType(e);
3395 PrepareTransparentEncryptionFinallyBlock( closeDataReader: processFinallyBlockAsync,
3396 decrementAsyncCount: decrementAsyncCountInFinallyBlockAsync,
3397 clearDataStructures: processFinallyBlockAsync,
3398 wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded,
3399 describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap,
3400 describeParameterEncryptionDataReader: describeParameterEncryptionDataReader);
3403 onFailure: ((exception) => {
3404 if (_cachedAsyncState != null) {
3405 _cachedAsyncState.ResetAsyncState();
3407 if (exception != null) {
3411 decrementAsyncCountInFinallyBlock = false;
3414 // If it was async, ending the reader is still pending.
3416 // Mark that we should not process the finally block since we have async execution pending.
3417 // Note that this should be done outside the task's continuation delegate.
3418 processFinallyBlock = false;
3419 returnTask = Task.Run(() => {
3420 bool processFinallyBlockAsync = true;
3421 bool decrementAsyncCountInFinallyBlockAsync = true;
3423 RuntimeHelpers.PrepareConstrainedRegions();
3426 TdsParser.ReliabilitySection tdsReliabilitySectionAsync = new TdsParser.ReliabilitySection();
3427 RuntimeHelpers.PrepareConstrainedRegions();
3429 tdsReliabilitySectionAsync.Start();
3432 // Check for any exceptions on network write, before reading.
3433 CheckThrowSNIException();
3435 // If it is async, then TryFetchInputParameterEncryptionInfo-> RunExecuteReaderTds would have incremented the async count.
3436 // Decrement it when we are about to complete async execute reader.
3437 SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection();
3438 if (internalConnectionTds != null) {
3439 internalConnectionTds.DecrementAsyncCount();
3440 decrementAsyncCountInFinallyBlockAsync = false;
3443 // Complete executereader.
3444 describeParameterEncryptionDataReader = CompleteAsyncExecuteReader(forDescribeParameterEncryption: true);
3445 Debug.Assert(null == _stateObj, "non-null state object in PrepareForTransparentEncryption.");
3447 // Read the results of describe parameter encryption.
3448 ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap);
3450 // Failpoint to force the thread to halt to simulate cancellation of SqlCommand.
3451 if (_sleepAfterReadDescribeEncryptionParameterResults) {
3452 Thread.Sleep(10000);
3458 tdsReliabilitySectionAsync.Stop();
3462 catch (Exception e) {
3463 processFinallyBlockAsync = ADP.IsCatchableExceptionType(e);
3467 PrepareTransparentEncryptionFinallyBlock( closeDataReader: processFinallyBlockAsync,
3468 decrementAsyncCount: decrementAsyncCountInFinallyBlockAsync,
3469 clearDataStructures: processFinallyBlockAsync,
3470 wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded,
3471 describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap,
3472 describeParameterEncryptionDataReader: describeParameterEncryptionDataReader);
3476 decrementAsyncCountInFinallyBlock = false;
3479 // For synchronous execution, read the results of describe parameter encryption here.
3480 ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap);
3484 // Failpoint to force the thread to halt to simulate cancellation of SqlCommand.
3485 if (_sleepAfterReadDescribeEncryptionParameterResults) {
3486 Thread.Sleep(10000);
3491 catch (Exception e) {
3492 processFinallyBlock = ADP.IsCatchableExceptionType(e);
3493 exceptionCaught = true;
3497 // Free up the state only for synchronous execution. For asynchronous execution, free only if there was an exception.
3498 PrepareTransparentEncryptionFinallyBlock(closeDataReader: (processFinallyBlock && !async) || exceptionCaught,
3499 decrementAsyncCount: decrementAsyncCountInFinallyBlock && exceptionCaught,
3500 clearDataStructures: (processFinallyBlock && !async) || exceptionCaught,
3501 wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded,
3502 describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap,
3503 describeParameterEncryptionDataReader: describeParameterEncryptionDataReader);
3508 tdsReliabilitySection.Stop();
3512 catch (System.OutOfMemoryException e) {
3513 _activeConnection.Abort(e);
3516 catch (System.StackOverflowException e) {
3517 _activeConnection.Abort(e);
3520 catch (System.Threading.ThreadAbortException e) {
3521 _activeConnection.Abort(e);
3522 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
3525 catch (Exception e) {
3526 if (cachedAsyncState != null) {
3527 cachedAsyncState.ResetAsyncState();
3530 if (ADP.IsCatchableExceptionType(e)) {
3531 ReliablePutStateObject();
3539 /// Executes an RPC to fetch param encryption info from SQL Engine. If this method is not done writing
3540 /// the request to wire, it'll set the "task" parameter which can be used to create continuations.
3542 /// <param name="timeout"></param>
3543 /// <param name="async"></param>
3544 /// <param name="asyncWrite"></param>
3545 /// <param name="inputParameterEncryptionNeeded"></param>
3546 /// <param name="task"></param>
3547 /// <param name="describeParameterEncryptionRpcOriginalRpcMap"></param>
3548 /// <returns></returns>
3549 private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout,
3552 out bool inputParameterEncryptionNeeded,
3554 out ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap) {
3555 inputParameterEncryptionNeeded = false;
3557 describeParameterEncryptionRpcOriginalRpcMap = null;
3560 // Count the rpc requests that need to be transparently encrypted
3561 // We simply look for any parameters in a request and add the request to be queried for parameter encryption
3562 Dictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcDictionary = new Dictionary<_SqlRPC, _SqlRPC>();
3564 for (int i = 0; i < _SqlRPCBatchArray.Length; i++) {
3565 // In BatchRPCMode, the actual T-SQL query is in the first parameter and not present as the rpcName, as is the case with non-BatchRPCMode.
3566 // So input parameters start at parameters[1]. parameters[0] is the actual T-SQL Statement. rpcName is sp_executesql.
3567 if (_SqlRPCBatchArray[i].parameters.Length > 1) {
3568 _SqlRPCBatchArray[i].needsFetchParameterEncryptionMetadata = true;
3570 // Since we are going to need multiple RPC objects, allocate a new one here for each command in the batch.
3571 _SqlRPC rpcDescribeParameterEncryptionRequest = new _SqlRPC();
3573 // Prepare the describe parameter encryption request.
3574 PrepareDescribeParameterEncryptionRequest(_SqlRPCBatchArray[i], ref rpcDescribeParameterEncryptionRequest);
3575 Debug.Assert(rpcDescribeParameterEncryptionRequest != null, "rpcDescribeParameterEncryptionRequest should not be null, after call to PrepareDescribeParameterEncryptionRequest.");
3577 Debug.Assert(!describeParameterEncryptionRpcOriginalRpcDictionary.ContainsKey(rpcDescribeParameterEncryptionRequest),
3578 "There should not already be a key referring to the current rpcDescribeParameterEncryptionRequest, in the dictionary describeParameterEncryptionRpcOriginalRpcDictionary.");
3580 // Add the describe parameter encryption RPC request as the key and its corresponding original rpc request to the dictionary.
3581 describeParameterEncryptionRpcOriginalRpcDictionary.Add(rpcDescribeParameterEncryptionRequest, _SqlRPCBatchArray[i]);
3585 describeParameterEncryptionRpcOriginalRpcMap = new ReadOnlyDictionary<_SqlRPC, _SqlRPC>(describeParameterEncryptionRpcOriginalRpcDictionary);
3587 if (describeParameterEncryptionRpcOriginalRpcMap.Count == 0) {
3588 // If no parameters are present, nothing to do, simply return.
3592 inputParameterEncryptionNeeded = true;
3595 _sqlRPCParameterEncryptionReqArray = describeParameterEncryptionRpcOriginalRpcMap.Keys.ToArray();
3597 Debug.Assert(_sqlRPCParameterEncryptionReqArray.Length > 0, "There should be at-least 1 describe parameter encryption rpc request.");
3598 Debug.Assert(_sqlRPCParameterEncryptionReqArray.Length <= _SqlRPCBatchArray.Length,
3599 "The number of decribe parameter encryption RPC requests is more than the number of original RPC requests.");
3601 else if (0 != GetParameterCount(_parameters)) {
3602 // Fetch params for a single batch
3603 inputParameterEncryptionNeeded = true;
3604 _sqlRPCParameterEncryptionReqArray = new _SqlRPC[1];
3607 GetRPCObject(_parameters.Count, ref rpc);
3608 Debug.Assert(rpc != null, "GetRPCObject should not return rpc as null.");
3610 rpc.rpcName = CommandText;
3613 foreach (SqlParameter sqlParam in _parameters) {
3614 rpc.parameters[i++] = sqlParam;
3617 // Prepare the RPC request for describe parameter encryption procedure.
3618 PrepareDescribeParameterEncryptionRequest(rpc, ref _sqlRPCParameterEncryptionReqArray[0]);
3619 Debug.Assert(_sqlRPCParameterEncryptionReqArray[0] != null, "_sqlRPCParameterEncryptionReqArray[0] should not be null, after call to PrepareDescribeParameterEncryptionRequest.");
3622 if (inputParameterEncryptionNeeded) {
3623 // Set the flag that indicates that parameter encryption requests are currently in-progress.
3624 _isDescribeParameterEncryptionRPCCurrentlyInProgress = true;
3627 // Failpoint to force the thread to halt to simulate cancellation of SqlCommand.
3628 if (_sleepDuringTryFetchInputParameterEncryptionInfo) {
3629 Thread.Sleep(10000);
3634 return RunExecuteReaderTds( CommandBehavior.Default,
3635 runBehavior: RunBehavior.ReturnImmediately, // Other RunBehavior modes will skip reading rows.
3640 asyncWrite: asyncWrite,
3643 describeParameterEncryptionRequest: true);
3651 /// Constructs a SqlParameter with a given string value
3653 /// <param name="queryText"></param>
3654 /// <returns></returns>
3655 private SqlParameter GetSqlParameterWithQueryText(string queryText)
3657 SqlParameter sqlParam = new SqlParameter(null, ((queryText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, queryText.Length);
3658 sqlParam.Value = queryText;
3664 /// Constructs the sp_describe_parameter_encryption request with the values from the original RPC call.
3665 /// Prototype for <sp_describe_parameter_encryption> is
3666 /// exec sp_describe_parameter_encryption @tsql=N'[SQL Statement]', @params=N'@p1 varbinary(256)'
3668 /// <param name="originalRpcRequest">Original RPC request</param>
3669 /// <param name="describeParameterEncryptionRequest">sp_describe_parameter_encryption request being built</param>
3670 private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcRequest, ref _SqlRPC describeParameterEncryptionRequest) {
3671 Debug.Assert(originalRpcRequest != null);
3673 // Construct the RPC request for sp_describe_parameter_encryption
3674 // sp_describe_parameter_encryption always has 2 parameters (stmt, paramlist).
3675 GetRPCObject(2, ref describeParameterEncryptionRequest, forSpDescribeParameterEncryption:true);
3676 describeParameterEncryptionRequest.rpcName = "sp_describe_parameter_encryption";
3678 // Prepare @tsql parameter
3679 SqlParameter sqlParam;
3682 // In BatchRPCMode, The actual T-SQL query is in the first parameter and not present as the rpcName, as is the case with non-BatchRPCMode.
3684 Debug.Assert(originalRpcRequest.parameters != null && originalRpcRequest.parameters.Length > 0,
3685 "originalRpcRequest didn't have at-least 1 parameter in BatchRPCMode, in PrepareDescribeParameterEncryptionRequest.");
3686 text = (string)originalRpcRequest.parameters[0].Value;
3687 sqlParam = GetSqlParameterWithQueryText(text);
3690 text = originalRpcRequest.rpcName;
3691 if (CommandType == Data.CommandType.StoredProcedure) {
3692 // For stored procedures, we need to prepare @tsql in the following format
3693 // N'EXEC sp_name @param1=@param1, @param1=@param2, ..., @paramN=@paramN'
3694 sqlParam = BuildStoredProcedureStatementForColumnEncryption(text, originalRpcRequest.parameters);
3697 sqlParam = GetSqlParameterWithQueryText(text);
3701 Debug.Assert(text != null, "@tsql parameter is null in PrepareDescribeParameterEncryptionRequest.");
3703 describeParameterEncryptionRequest.parameters[0] = sqlParam;
3704 string parameterList = null;
3706 // In BatchRPCMode, the input parameters start at parameters[1]. parameters[0] is the T-SQL statement. rpcName is sp_executesql.
3707 // And it is already in the format expected out of BuildParamList, which is not the case with Non-BatchRPCMode.
3709 if (originalRpcRequest.parameters.Length > 1) {
3710 parameterList = (string)originalRpcRequest.parameters[1].Value;
3714 // Prepare @params parameter
3715 // Need to create new parameters as we cannot have the same parameter being part of two SqlCommand objects
3716 SqlParameter paramCopy;
3717 SqlParameterCollection tempCollection = new SqlParameterCollection();
3719 for (int i = 0; i < _parameters.Count; i++) {
3720 SqlParameter param = originalRpcRequest.parameters[i];
3721 paramCopy = new SqlParameter(param.ParameterName, param.SqlDbType, param.Size, param.Direction, param.Precision, param.Scale, param.SourceColumn, param.SourceVersion,
3722 param.SourceColumnNullMapping, param.Value, param.XmlSchemaCollectionDatabase, param.XmlSchemaCollectionOwningSchema, param.XmlSchemaCollectionName);
3723 paramCopy.CompareInfo = param.CompareInfo;
3724 paramCopy.TypeName = param.TypeName;
3725 paramCopy.UdtTypeName = param.UdtTypeName;
3726 paramCopy.IsNullable = param.IsNullable;
3727 paramCopy.LocaleId = param.LocaleId;
3728 paramCopy.Offset = param.Offset;
3730 tempCollection.Add(paramCopy);
3733 Debug.Assert(_stateObj == null, "_stateObj should be null at this time, in PrepareDescribeParameterEncryptionRequest.");
3734 Debug.Assert(_activeConnection != null, "_activeConnection should not be null at this time, in PrepareDescribeParameterEncryptionRequest.");
3735 TdsParser tdsParser = null;
3737 if (_activeConnection.Parser != null) {
3738 tdsParser = _activeConnection.Parser;
3739 if ((tdsParser == null) || (tdsParser.State == TdsParserState.Broken) || (tdsParser.State == TdsParserState.Closed)) {
3740 // Connection's parser is null as well, therefore we must be closed
3741 throw ADP.ClosedConnectionError();
3745 parameterList = BuildParamList(tdsParser, tempCollection, includeReturnValue:true);
3748 Debug.Assert(!string.IsNullOrWhiteSpace(parameterList), "parameterList should not be null or empty or whitespace.");
3750 sqlParam = new SqlParameter(null, ((parameterList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, parameterList.Length);
3751 sqlParam.Value = parameterList;
3752 describeParameterEncryptionRequest.parameters[1] = sqlParam;
3756 /// Read the output of sp_describe_parameter_encryption
3758 /// <param name="ds">Resultset from calling to sp_describe_parameter_encryption</param>
3759 /// <param name="describeParameterEncryptionRpcOriginalRpcMap"> Readonly dictionary with the map of parameter encryption rpc requests with the corresponding original rpc requests.</param>
3760 private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap) {
3762 int currentOrdinal = -1;
3763 SqlTceCipherInfoEntry cipherInfoEntry;
3764 Dictionary<int, SqlTceCipherInfoEntry> columnEncryptionKeyTable = new Dictionary<int, SqlTceCipherInfoEntry>();
3766 Debug.Assert((describeParameterEncryptionRpcOriginalRpcMap != null) == BatchRPCMode,
3767 "describeParameterEncryptionRpcOriginalRpcMap should be non-null if and only if it is BatchRPCMode.");
3769 // Indicates the current result set we are reading, used in BatchRPCMode, where we can have more than 1 result set.
3770 int resultSetSequenceNumber = 0;
3773 // Keep track of the number of rows in the result sets.
3774 int rowsAffected = 0;
3777 // A flag that used in BatchRPCMode, to assert the result of lookup in to the dictionary maintaining the map of describe parameter encryption requests
3778 // and the corresponding original rpc requests.
3779 bool lookupDictionaryResult;
3783 // If we got more RPC results from the server than what was requested.
3784 if (resultSetSequenceNumber >= _sqlRPCParameterEncryptionReqArray.Length) {
3785 Debug.Assert(false, "Server sent back more results than what was expected for describe parameter encryption requests in BatchRPCMode.");
3786 // Ignore the rest of the results from the server, if for whatever reason it sends back more than what we expect.
3791 // First read the column encryption key list
3798 // Column Encryption Key Ordinal.
3799 currentOrdinal = ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyOrdinal);
3800 Debug.Assert(currentOrdinal >= 0, "currentOrdinal cannot be negative.");
3802 // Try to see if there was already an entry for the current ordinal.
3803 if (!columnEncryptionKeyTable.TryGetValue(currentOrdinal, out cipherInfoEntry)) {
3804 // If an entry for this ordinal was not found, create an entry in the columnEncryptionKeyTable for this ordinal.
3805 cipherInfoEntry = new SqlTceCipherInfoEntry(currentOrdinal);
3806 columnEncryptionKeyTable.Add(currentOrdinal, cipherInfoEntry);
3809 Debug.Assert(!cipherInfoEntry.Equals(default(SqlTceCipherInfoEntry)), "cipherInfoEntry should not be un-initialized.");
3812 byte[] encryptedKey = null;
3813 int encryptedKeyLength = (int)ds.GetBytes((int)DescribeParameterEncryptionResultSet1.EncryptedKey, 0, encryptedKey, 0, 0);
3814 encryptedKey = new byte[encryptedKeyLength];
3815 ds.GetBytes((int)DescribeParameterEncryptionResultSet1.EncryptedKey, 0, encryptedKey, 0, encryptedKeyLength);
3817 // Read the metadata version of the key.
3818 // It should always be 8 bytes.
3819 byte[] keyMdVersion = new byte[8];
3820 ds.GetBytes((int)DescribeParameterEncryptionResultSet1.KeyMdVersion, 0, keyMdVersion, 0, keyMdVersion.Length);
3822 // Validate the provider name
3823 string providerName = ds.GetString((int)DescribeParameterEncryptionResultSet1.ProviderName);
3824 //SqlColumnEncryptionKeyStoreProvider keyStoreProvider;
3825 //if (!SqlConnection.TryGetColumnEncryptionKeyStoreProvider (providerName, out keyStoreProvider)) {
3826 // // unknown provider, skip processing this cek.
3827 // Bid.Trace("<sc.SqlCommand.ReadDescribeEncryptionParameterResults|INFO>Unknown provider name recevied %s, skipping\n", providerName);
3831 cipherInfoEntry.Add(encryptedKey: encryptedKey,
3832 databaseId: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.DbId),
3833 cekId: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyId),
3834 cekVersion: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyVersion),
3835 cekMdVersion: keyMdVersion,
3836 keyPath: ds.GetString((int)DescribeParameterEncryptionResultSet1.KeyPath),
3837 keyStoreName: providerName,
3838 algorithmName: ds.GetString((int)DescribeParameterEncryptionResultSet1.KeyEncryptionAlgorithm));
3841 if (!ds.NextResult()) {
3842 throw SQL.UnexpectedDescribeParamFormat ();
3845 // Find the RPC command that generated this tce request
3847 Debug.Assert(_sqlRPCParameterEncryptionReqArray[resultSetSequenceNumber] != null, "_sqlRPCParameterEncryptionReqArray[resultSetSequenceNumber] should not be null.");
3849 // Lookup in the dictionary to get the original rpc request corresponding to the describe parameter encryption request
3850 // pointed to by _sqlRPCParameterEncryptionReqArray[resultSetSequenceNumber]
3852 lookupDictionaryResult = describeParameterEncryptionRpcOriginalRpcMap.TryGetValue(_sqlRPCParameterEncryptionReqArray[resultSetSequenceNumber++], out rpc);
3854 Debug.Assert(lookupDictionaryResult,
3855 "Describe Parameter Encryption RPC request key must be present in the dictionary describeParameterEncryptionRpcOriginalRpcMap");
3856 Debug.Assert(rpc != null,
3857 "Describe Parameter Encryption RPC request's corresponding original rpc request must not be null in the dictionary describeParameterEncryptionRpcOriginalRpcMap");
3860 rpc = _rpcArrayOf1[0];
3863 Debug.Assert(rpc != null, "rpc should not be null here.");
3865 // This is the index in the parameters array where the actual parameters start.
3866 // In BatchRPCMode, parameters[0] has the t-sql, parameters[1] has the param list
3867 // and actual parameters of the query start at parameters[2].
3868 int parameterStartIndex = (BatchRPCMode ? 2 : 0);
3870 // Iterate over the parameter names to read the encryption type info
3876 Debug.Assert(rpc != null, "Describe Parameter Encryption requested for non-tce spec proc");
3877 string parameterName = ds.GetString((int)DescribeParameterEncryptionResultSet2.ParameterName);
3879 // When the RPC object gets reused, the parameter array has more parameters that the valid params for the command.
3880 // Null is used to indicate the end of the valid part of the array. Refer to GetRPCObject().
3881 for (paramIdx = parameterStartIndex; paramIdx < rpc.parameters.Length && rpc.parameters[paramIdx] != null; paramIdx++) {
3882 SqlParameter sqlParameter = rpc.parameters[paramIdx];
3883 Debug.Assert(sqlParameter != null, "sqlParameter should not be null.");
3885 if (sqlParameter.ParameterNameFixed.Equals(parameterName, StringComparison.Ordinal)) {
3886 Debug.Assert(sqlParameter.CipherMetadata == null, "param.CipherMetadata should be null.");
3887 sqlParameter.HasReceivedMetadata = true;
3889 // Found the param, setup the encryption info.
3890 byte columnEncryptionType = ds.GetByte((int)DescribeParameterEncryptionResultSet2.ColumnEncrytionType);
3891 if ((byte)SqlClientEncryptionType.PlainText != columnEncryptionType) {
3892 byte cipherAlgorithmId = ds.GetByte((int)DescribeParameterEncryptionResultSet2.ColumnEncryptionAlgorithm);
3893 int columnEncryptionKeyOrdinal = ds.GetInt32((int)DescribeParameterEncryptionResultSet2.ColumnEncryptionKeyOrdinal);
3894 byte columnNormalizationRuleVersion = ds.GetByte((int)DescribeParameterEncryptionResultSet2.NormalizationRuleVersion);
3896 // Lookup the key, failing which throw an exception
3897 if (!columnEncryptionKeyTable.TryGetValue(columnEncryptionKeyOrdinal, out cipherInfoEntry)) {
3898 throw SQL.InvalidEncryptionKeyOrdinal(columnEncryptionKeyOrdinal, columnEncryptionKeyTable.Count);
3901 sqlParameter.CipherMetadata = new SqlCipherMetadata(sqlTceCipherInfoEntry: cipherInfoEntry,
3902 ordinal: unchecked((ushort)-1),
3903 cipherAlgorithmId: cipherAlgorithmId,
3904 cipherAlgorithmName: null,
3905 encryptionType: columnEncryptionType,
3906 normalizationRuleVersion: columnNormalizationRuleVersion);
3908 // Decrypt the symmetric key.(This will also validate and throw if needed).
3909 Debug.Assert(_activeConnection != null, @"_activeConnection should not be null");
3910 SqlSecurityUtility.DecryptSymmetricKey(sqlParameter.CipherMetadata, this._activeConnection.DataSource);
3912 // This is effective only for BatchRPCMode even though we set it for non-BatchRPCMode also,
3913 // since for non-BatchRPCMode mode, paramoptions gets thrown away and reconstructed in BuildExecuteSql.
3914 rpc.paramoptions[paramIdx] |= TdsEnums.RPC_PARAM_ENCRYPTED;
3922 // When the RPC object gets reused, the parameter array has more parameters that the valid params for the command.
3923 // Null is used to indicate the end of the valid part of the array. Refer to GetRPCObject().
3924 for (paramIdx = parameterStartIndex; paramIdx < rpc.parameters.Length && rpc.parameters[paramIdx] != null; paramIdx++) {
3925 if (!rpc.parameters[paramIdx].HasReceivedMetadata && rpc.parameters[paramIdx].Direction != ParameterDirection.ReturnValue) {
3926 // Encryption MD wasn't sent by the server - we expect the metadata to be sent for all the parameters
3927 // that were sent in the original sp_describe_parameter_encryption but not necessarily for return values,
3928 // since there might be multiple return values but server will only send for one of them.
3929 // For parameters that don't need encryption, the encryption type is set to plaintext.
3930 throw SQL.ParamEncryptionMetadataMissing(rpc.parameters[paramIdx].ParameterName, rpc.GetCommandTextOrRpcName());
3935 Debug.Assert(rowsAffected == RowsAffectedByDescribeParameterEncryption,
3936 "number of rows received for describe parameter encryption should be equal to rows affected by describe parameter encryption.");
3939 // The server has responded with encryption related information for this rpc request. So clear the needsFetchParameterEncryptionMetadata flag.
3940 rpc.needsFetchParameterEncryptionMetadata = false;
3941 } while (ds.NextResult());
3943 // Verify that we received response for each rpc call needs tce
3945 for (int i = 0; i < _SqlRPCBatchArray.Length; i++) {
3946 if (_SqlRPCBatchArray[i].needsFetchParameterEncryptionMetadata) {
3947 throw SQL.ProcEncryptionMetadataMissing(_SqlRPCBatchArray[i].rpcName);
3952 // If we are not in Batch RPC mode, update the query cache with the encryption MD.
3953 if (!BatchRPCMode) {
3954 SqlQueryMetadataCache.GetInstance().AddQueryMetadata(this, ignoreQueriesWithReturnValueParams: true);
3958 internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream, string method) {
3959 Task unused; // sync execution
3961 SqlDataReader reader = RunExecuteReader(cmdBehavior, runBehavior, returnStream, method, completion: null, timeout: CommandTimeout, task: out unused, usedCache: out usedCache);
3962 Debug.Assert(unused == null, "returned task during synchronous execution");
3966 // task is created in case of pending asynchronous write, returned SqlDataReader should not be utilized until that task is complete
3967 internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream, string method, TaskCompletionSource<object> completion, int timeout, out Task task, out bool usedCache, bool asyncWrite = false, bool inRetry = false) {
3968 bool async = (null != completion);
3974 _rowsAffectedBySpDescribeParameterEncryption = -1;
3976 if (0 != (CommandBehavior.SingleRow & cmdBehavior)) {
3977 // CommandBehavior.SingleRow implies CommandBehavior.SingleResult
3978 cmdBehavior |= CommandBehavior.SingleResult;
3981 // @devnote: this function may throw for an invalid connection
3982 // @devnote: returns false for empty command text
3984 ValidateCommand(method, async);
3987 CheckNotificationStateAndAutoEnlist(); // Only call after validate - requires non null connection!
3989 TdsParser bestEffortCleanupTarget = null;
3990 // This section needs to occur AFTER ValidateCommand - otherwise it will AV without a connection.
3991 RuntimeHelpers.PrepareConstrainedRegions();
3994 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
3996 RuntimeHelpers.PrepareConstrainedRegions();
3998 tdsReliabilitySection.Start();
4002 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
4003 SqlStatistics statistics = Statistics;
4004 if (null != statistics) {
4005 if ((!this.IsDirty && this.IsPrepared && !_hiddenPrepare)
4006 || (this.IsPrepared && _execType == EXECTYPE.PREPAREPENDING))
4008 statistics.SafeIncrement(ref statistics._preparedExecs);
4011 statistics.SafeIncrement(ref statistics._unpreparedExecs);
4015 // Reset the encryption related state of the command and its parameters.
4016 ResetEncryptionState();
4018 if ( _activeConnection.IsContextConnection ) {
4019 return RunExecuteReaderSmi( cmdBehavior, runBehavior, returnStream );
4021 else if (IsColumnEncryptionEnabled) {
4022 Task returnTask = null;
4023 PrepareForTransparentEncryption(cmdBehavior, returnStream, async, timeout, completion, out returnTask, asyncWrite && async, out usedCache, inRetry);
4024 Debug.Assert(usedCache || (async == (returnTask != null)), @"if we didn't use the cache, returnTask should be null if and only if async is false.");
4026 long firstAttemptStart = ADP.TimerCurrent();
4029 return RunExecuteReaderTdsWithTransparentParameterEncryption(cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite && async, inRetry: inRetry, ds: null,
4030 describeParameterEncryptionRequest: false, describeParameterEncryptionTask: returnTask);
4032 catch (SqlException ex) {
4033 // We only want to retry once, so don't retry if we are already in retry.
4034 // If we didn't use the cache, we don't want to retry.
4035 // The async retried are handled separately, handle only sync calls here.
4036 if (inRetry || async || !usedCache) {
4040 bool shouldRetry = false;
4042 // Check if we have an error indicating that we can retry.
4043 for (int i = 0; i < ex.Errors.Count; i++) {
4044 if (ex.Errors[i].Number == TdsEnums.TCE_CONVERSION_ERROR_CLIENT_RETRY) {
4054 // Retry if the command failed with appropriate error.
4055 // First invalidate the entry from the cache, so that we refresh our encryption MD.
4056 SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this);
4057 return RunExecuteReader(cmdBehavior, runBehavior, returnStream, method, null, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, async, inRetry: true);
4062 return RunExecuteReaderTds( cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite && async, inRetry: inRetry);
4068 tdsReliabilitySection.Stop();
4072 catch (System.OutOfMemoryException e) {
4073 _activeConnection.Abort(e);
4076 catch (System.StackOverflowException e) {
4077 _activeConnection.Abort(e);
4080 catch (System.Threading.ThreadAbortException e) {
4081 _activeConnection.Abort(e);
4082 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
4088 /// RunExecuteReaderTds after Transparent Parameter Encryption is complete.
4090 /// <param name="cmdBehavior"></param>
4091 /// <param name="runBehavior"></param>
4092 /// <param name="returnStream"></param>
4093 /// <param name="async"></param>
4094 /// <param name="timeout"></param>
4095 /// <param name="task"></param>
4096 /// <param name="asyncWrite"></param>
4097 /// <param name="ds"></param>
4098 /// <param name="describeParameterEncryptionRequest"></param>
4099 /// <param name="describeParameterEncryptionTask"></param>
4100 /// <returns></returns>
4101 private SqlDataReader RunExecuteReaderTdsWithTransparentParameterEncryption(CommandBehavior cmdBehavior,
4102 RunBehavior runBehavior,
4109 SqlDataReader ds=null,
4110 bool describeParameterEncryptionRequest = false,
4111 Task describeParameterEncryptionTask = null) {
4112 Debug.Assert(!asyncWrite || async, "AsyncWrite should be always accompanied by Async");
4114 if (ds == null && returnStream) {
4115 ds = new SqlDataReader(this, cmdBehavior);
4118 if (describeParameterEncryptionTask != null) {
4119 long parameterEncryptionStart = ADP.TimerCurrent();
4120 TaskCompletionSource<object> completion = new TaskCompletionSource<object>();
4121 AsyncHelper.ContinueTask(describeParameterEncryptionTask, completion,
4123 Task subTask = null;
4124 RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, TdsParserStaticMethods.GetRemainingTimeout(timeout, parameterEncryptionStart), out subTask, asyncWrite, inRetry, ds);
4125 if (subTask == null) {
4126 completion.SetResult(null);
4129 AsyncHelper.ContinueTask(subTask, completion, () => completion.SetResult(null));
4131 }, connectionToDoom: null,
4132 onFailure: ((exception) => {
4133 if (_cachedAsyncState != null) {
4134 _cachedAsyncState.ResetAsyncState();
4136 if (exception != null) {
4140 onCancellation: (() => {
4141 if (_cachedAsyncState != null) {
4142 _cachedAsyncState.ResetAsyncState();
4145 connectionToAbort: _activeConnection);
4146 task = completion.Task;
4150 // Synchronous execution.
4151 return RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite, inRetry, ds);
4155 private SqlDataReader RunExecuteReaderTds( CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream, bool async, int timeout, out Task task, bool asyncWrite, bool inRetry, SqlDataReader ds=null, bool describeParameterEncryptionRequest = false) {
4156 Debug.Assert(!asyncWrite || async, "AsyncWrite should be always accompanied by Async");
4158 if (ds == null && returnStream) {
4159 ds = new SqlDataReader(this, cmdBehavior);
4162 Task reconnectTask = _activeConnection.ValidateAndReconnect(null, timeout);
4164 if (reconnectTask != null) {
4165 long reconnectionStart = ADP.TimerCurrent();
4167 TaskCompletionSource<object> completion = new TaskCompletionSource<object>();
4168 _activeConnection.RegisterWaitingForReconnect(completion.Task);
4169 _reconnectionCompletionSource = completion;
4170 CancellationTokenSource timeoutCTS = new CancellationTokenSource();
4171 AsyncHelper.SetTimeoutException(completion, timeout, SQL.CR_ReconnectTimeout, timeoutCTS.Token);
4172 AsyncHelper.ContinueTask(reconnectTask, completion,
4174 if (completion.Task.IsCompleted) {
4177 Interlocked.CompareExchange(ref _reconnectionCompletionSource, null, completion);
4178 timeoutCTS.Cancel();
4180 RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, TdsParserStaticMethods.GetRemainingTimeout(timeout, reconnectionStart), out subTask, asyncWrite, inRetry, ds);
4181 if (subTask == null) {
4182 completion.SetResult(null);
4185 AsyncHelper.ContinueTask(subTask, completion, () => completion.SetResult(null));
4187 }, connectionToAbort: _activeConnection);
4188 task = completion.Task;
4192 AsyncHelper.WaitForCompletion(reconnectTask, timeout, () => { throw SQL.CR_ReconnectTimeout(); });
4193 timeout = TdsParserStaticMethods.GetRemainingTimeout(timeout, reconnectionStart);
4197 // make sure we have good parameter information
4198 // prepare the command
4200 Debug.Assert(null != _activeConnection.Parser, "TdsParser class should not be null in Command.Execute!");
4202 bool inSchema = (0 != (cmdBehavior & CommandBehavior.SchemaOnly));
4209 string optionSettings = null;
4210 bool processFinallyBlock = true;
4211 bool decrementAsyncCountOnFailure = false;
4213 // If we are in retry, don't increment the Async count. This should have already been set.
4214 if (async && !inRetry) {
4215 _activeConnection.GetOpenTdsConnection().IncrementAsyncCount();
4216 decrementAsyncCountOnFailure = true;
4222 _activeConnection.AddWeakReference(this, SqlReferenceCollection.CommandTag);
4226 Task writeTask = null;
4228 if (describeParameterEncryptionRequest) {
4230 if (_sleepDuringRunExecuteReaderTdsForSpDescribeParameterEncryption) {
4231 Thread.Sleep(10000);
4235 Debug.Assert(_sqlRPCParameterEncryptionReqArray != null, "RunExecuteReader rpc array not provided for describe parameter encryption request.");
4236 writeTask = _stateObj.Parser.TdsExecuteRPC(this, _sqlRPCParameterEncryptionReqArray, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync: !asyncWrite);
4238 else if (BatchRPCMode) {
4239 Debug.Assert(inSchema == false, "Batch RPC does not support schema only command beahvior");
4240 Debug.Assert(!IsPrepared, "Batch RPC should not be prepared!");
4241 Debug.Assert(!IsDirty, "Batch RPC should not be marked as dirty!");
4242 //Currently returnStream is always false, but we may want to return a Reader later.
4243 //if (returnStream) {
4244 // Bid.Trace("<sc.SqlCommand.ExecuteReader|INFO> %d#, Command executed as batch RPC.\n", ObjectID);
4246 Debug.Assert(_SqlRPCBatchArray != null, "RunExecuteReader rpc array not provided");
4247 writeTask = _stateObj.Parser.TdsExecuteRPC(this, _SqlRPCBatchArray, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync: !asyncWrite );
4249 else if ((System.Data.CommandType.Text == this.CommandType) && (0 == GetParameterCount(_parameters))) {
4250 // Send over SQL Batch command if we are not a stored proc and have no parameters
4252 Debug.Assert(!IsUserPrepared, "CommandType.Text with no params should not be prepared!");
4254 Bid.Trace("<sc.SqlCommand.ExecuteReader|INFO> %d#, Command executed as SQLBATCH.\n", ObjectID);
4256 string text = GetCommandText(cmdBehavior) + GetResetOptionsString(cmdBehavior);
4257 writeTask = _stateObj.Parser.TdsExecuteSQLBatch(text, timeout, this.Notification, _stateObj, sync: !asyncWrite);
4259 else if (System.Data.CommandType.Text == this.CommandType) {
4261 Debug.Assert(_cachedMetaData == null || !_dirty, "dirty query should not have cached metadata!"); // can have cached metadata if dirty because of parameters
4263 // someone changed the command text or the parameter schema so we must unprepare the command
4265 // remeber that IsDirty includes test for IsPrepared!
4266 if(_execType == EXECTYPE.PREPARED) {
4267 _hiddenPrepare = true;
4273 if (_execType == EXECTYPE.PREPARED) {
4274 Debug.Assert(this.IsPrepared && (_prepareHandle != -1), "invalid attempt to call sp_execute without a handle!");
4275 rpc = BuildExecute(inSchema);
4277 else if (_execType == EXECTYPE.PREPAREPENDING) {
4278 Debug.Assert(_activeConnection.IsShiloh, "Invalid attempt to call sp_prepexec on non 7.x server");
4279 rpc = BuildPrepExec(cmdBehavior);
4280 // next time through, only do an exec
4281 _execType = EXECTYPE.PREPARED;
4282 _preparedConnectionCloseCount = _activeConnection.CloseCount;
4283 _preparedConnectionReconnectCount = _activeConnection.ReconnectCount;
4284 // mark ourselves as preparing the command
4288 Debug.Assert(_execType == EXECTYPE.UNPREPARED, "Invalid execType!");
4289 BuildExecuteSql(cmdBehavior, null, _parameters, ref rpc);
4292 // if shiloh, then set NOMETADATA_UNLESSCHANGED flag
4293 if (_activeConnection.IsShiloh)
4294 rpc.options = TdsEnums.RPC_NOMETADATA;
4296 Bid.Trace("<sc.SqlCommand.ExecuteReader|INFO> %d#, Command executed as RPC.\n", ObjectID);
4300 Debug.Assert(_rpcArrayOf1[0] == rpc);
4301 writeTask = _stateObj.Parser.TdsExecuteRPC(this, _rpcArrayOf1, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync:!asyncWrite);
4304 Debug.Assert(this.CommandType == System.Data.CommandType.StoredProcedure, "unknown command type!");
4305 // note: invalid asserts on Shiloh. On 8.0 (Shiloh) and above a command is ALWAYS prepared
4306 // and IsDirty is always set if there are changes and the command is marked Prepared!
4307 Debug.Assert(IsShiloh || !IsPrepared, "RPC should not be prepared!");
4308 Debug.Assert(IsShiloh || !IsDirty, "RPC should not be marked as dirty!");
4310 BuildRPC(inSchema, _parameters, ref rpc);
4312 // if we need to augment the command because a user has changed the command behavior (e.g. FillSchema)
4313 // then batch sql them over. This is inefficient (3 round trips) but the only way we can get metadata only from
4315 optionSettings = GetSetOptionsString(cmdBehavior);
4317 Bid.Trace("<sc.SqlCommand.ExecuteReader|INFO> %d#, Command executed as RPC.\n", ObjectID);
4319 // turn set options ON
4320 if (null != optionSettings) {
4321 Task executeTask = _stateObj.Parser.TdsExecuteSQLBatch(optionSettings, timeout, this.Notification, _stateObj, sync: true);
4322 Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
4324 Debug.Assert(_stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
4325 bool result = _stateObj.Parser.TryRun(RunBehavior.UntilDone, this, null, null, _stateObj, out dataReady);
4326 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
4327 // and turn OFF when the ds exhausts the stream on Close()
4328 optionSettings = GetResetOptionsString(cmdBehavior);
4331 // turn debugging on
4332 _activeConnection.CheckSQLDebug();
4335 Debug.Assert(_rpcArrayOf1[0] == rpc);
4336 writeTask=_stateObj.Parser.TdsExecuteRPC(this, _rpcArrayOf1, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync:!asyncWrite);
4339 Debug.Assert(writeTask == null || async, "Returned task in sync mode");
4342 decrementAsyncCountOnFailure = false;
4343 if (writeTask != null) {
4344 task = AsyncHelper.CreateContinuationTask(writeTask, () => {
4345 _activeConnection.GetOpenTdsConnection(); // it will throw if connection is closed
4346 cachedAsyncState.SetAsyncReaderState(ds, runBehavior, optionSettings);
4348 onFailure: (exc) => {
4349 _activeConnection.GetOpenTdsConnection().DecrementAsyncCount();
4353 cachedAsyncState.SetAsyncReaderState(ds, runBehavior, optionSettings);
4357 // Always execute - even if no reader!
4358 FinishExecuteReader(ds, runBehavior, optionSettings, isInternal: false, forDescribeParameterEncryption: false);
4361 catch (Exception e) {
4362 processFinallyBlock = ADP.IsCatchableExceptionType (e);
4363 if (decrementAsyncCountOnFailure) {
4364 SqlInternalConnectionTds innerConnectionTds = (_activeConnection.InnerConnection as SqlInternalConnectionTds);
4365 if (null != innerConnectionTds) { // it may be closed
4366 innerConnectionTds.DecrementAsyncCount();
4372 TdsParser.ReliabilitySection.Assert("unreliable call to RunExecuteReaderTds"); // you need to setup for a thread abort somewhere before you call this method
4373 if (processFinallyBlock && !async) {
4374 // When executing async, we need to keep the _stateObj alive...
4379 Debug.Assert(async || null == _stateObj, "non-null state object in RunExecuteReader");
4383 private SqlDataReader RunExecuteReaderSmi( CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream ) {
4384 SqlInternalConnectionSmi innerConnection = InternalSmiConnection;
4386 SmiEventStream eventStream = null;
4387 SqlDataReader ds = null;
4388 SmiRequestExecutor requestExecutor = null;
4390 // Set it up, process all of the events, and we're done!
4391 requestExecutor = SetUpSmiRequest( innerConnection );
4394 SysTx.Transaction transaction;
4395 innerConnection.GetCurrentTransactionPair(out transactionId, out transaction);
4397 if (Bid.AdvancedOn) {
4398 Bid.Trace("<sc.SqlCommand.RunExecuteReaderSmi|ADV> %d#, innerConnection=%d#, transactionId=0x%I64x, commandBehavior=%d.\n", ObjectID, innerConnection.ObjectID, transactionId, (int)cmdBehavior);
4401 if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion) {
4402 eventStream = requestExecutor.Execute(
4403 innerConnection.SmiConnection,
4407 SmiExecuteType.Reader
4411 eventStream = requestExecutor.Execute(
4412 innerConnection.SmiConnection,
4415 SmiExecuteType.Reader
4419 if ( ( runBehavior & RunBehavior.UntilDone ) != 0 ) {
4421 // Consume the results
4422 while( eventStream.HasEvents ) {
4423 eventStream.ProcessEvent( EventSink );
4425 eventStream.Close( EventSink );
4428 if ( returnStream ) {
4429 ds = new SqlDataReaderSmi( eventStream, this, cmdBehavior, innerConnection, EventSink, requestExecutor );
4430 ds.NextResult(); // Position on first set of results
4431 _activeConnection.AddWeakReference(ds, SqlReferenceCollection.DataReaderTag);
4434 EventSink.ProcessMessagesAndThrow();
4436 catch (Exception e) {
4437 // VSTS 159716 - we do not want to handle ThreadAbort, OutOfMemory or similar critical exceptions
4438 // because the state of used objects might remain invalid in this case
4439 if (!ADP.IsCatchableOrSecurityExceptionType(e)) {
4443 if (null != eventStream) {
4444 eventStream.Close( EventSink ); //
4447 if (requestExecutor != null) {
4448 requestExecutor.Close(EventSink);
4449 EventSink.ProcessMessagesAndThrow(ignoreNonFatalMessages: true);
4458 private SqlDataReader CompleteAsyncExecuteReader(bool isInternal = false, bool forDescribeParameterEncryption = false) {
4459 SqlDataReader ds = cachedAsyncState.CachedAsyncReader; // should not be null
4460 bool processFinallyBlock = true;
4462 FinishExecuteReader(ds, cachedAsyncState.CachedRunBehavior, cachedAsyncState.CachedSetOptions, isInternal, forDescribeParameterEncryption);
4464 catch (Exception e) {
4465 processFinallyBlock = ADP.IsCatchableExceptionType(e);
4469 TdsParser.ReliabilitySection.Assert("unreliable call to CompleteAsyncExecuteReader"); // you need to setup for a thread abort somewhere before you call this method
4470 if (processFinallyBlock) {
4471 // Don't reset the state for internal End. The user End will do that eventually.
4473 cachedAsyncState.ResetAsyncState();
4483 private void FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, string resetOptionsString, bool isInternal, bool forDescribeParameterEncryption) {
4484 // always wrap with a try { FinishExecuteReader(...) } finally { PutStateObject(); }
4486 // If this is not for internal usage, notify the dependency. If we have already initiated the end internally, the reader should be ready, so just return.
4487 if (!isInternal && !forDescribeParameterEncryption) {
4490 if (_internalEndExecuteInitiated) {
4491 Debug.Assert(_stateObj == null);
4496 if (runBehavior == RunBehavior.UntilDone) {
4499 Debug.Assert(_stateObj._syncOverAsync, "Should not attempt pends in a synchronous call");
4500 bool result = _stateObj.Parser.TryRun(RunBehavior.UntilDone, this, ds, null, _stateObj, out dataReady);
4501 if (!result) { throw SQL.SynchronousCallMayNotPend(); }
4503 catch (Exception e) {
4505 if (ADP.IsCatchableExceptionType(e)) {
4507 // The flag is expected to be reset by OnReturnValue. We should receive
4508 // the handle unless command execution failed. If fail, move back to pending
4510 _inPrepare = false; // reset the flag
4511 IsDirty = true; // mark command as dirty so it will be prepared next time we're comming through
4512 _execType = EXECTYPE.PREPAREPENDING; // reset execution type to pending
4523 // bind the parser to the reader if we get this far
4526 _stateObj = null; // the reader now owns this...
4527 ds.ResetOptionsString = resetOptionsString;
4533 // bind this reader to this connection now
4534 _activeConnection.AddWeakReference(ds, SqlReferenceCollection.DataReaderTag);
4536 // force this command to start reading data off the wire.
4537 // this will cause an error to be reported at Execute() time instead of Read() time
4538 // if the command is not set.
4540 _cachedMetaData = ds.MetaData;
4541 ds.IsInitialized = true; // Webdata 104560
4543 catch (Exception e) {
4545 if (ADP.IsCatchableExceptionType(e)) {
4547 // The flag is expected to be reset by OnReturnValue. We should receive
4548 // the handle unless command execution failed. If fail, move back to pending
4550 _inPrepare = false; // reset the flag
4551 IsDirty = true; // mark command as dirty so it will be prepared next time we're comming through
4552 _execType = EXECTYPE.PREPAREPENDING; // reset execution type to pending
4563 private void NotifyDependency() {
4564 if (_sqlDep != null) {
4565 _sqlDep.StartTimer(Notification);
4569 public SqlCommand Clone() {
4570 SqlCommand clone = new SqlCommand(this);
4571 Bid.Trace("<sc.SqlCommand.Clone|API> %d#, clone=%d#\n", ObjectID, clone.ObjectID);
4575 object ICloneable.Clone() {
4579 private void RegisterForConnectionCloseNotification<T>(ref Task<T> outterTask) {
4580 SqlConnection connection = _activeConnection;
4581 if (connection == null) {
4583 throw ADP.ClosedConnectionError();
4586 connection.RegisterForConnectionCloseNotification<T>(ref outterTask, this, SqlReferenceCollection.CommandTag);
4589 // validates that a command has commandText and a non-busy open connection
4590 // throws exception for error case, returns false if the commandText is empty
4591 private void ValidateCommand(string method, bool async) {
4592 if (null == _activeConnection) {
4593 throw ADP.ConnectionRequired(method);
4596 // Ensure that the connection is open and that the Parser is in the correct state
4597 SqlInternalConnectionTds tdsConnection = _activeConnection.InnerConnection as SqlInternalConnectionTds;
4599 // Ensure that if column encryption override was used then server supports its
4600 if (((SqlCommandColumnEncryptionSetting.UseConnectionSetting == ColumnEncryptionSetting && _activeConnection.IsColumnEncryptionSettingEnabled)
4601 || (ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.ResultSetOnly))
4602 && null != tdsConnection
4603 && null != tdsConnection.Parser
4604 && !tdsConnection.Parser.IsColumnEncryptionSupported) {
4605 throw SQL.TceNotSupported ();
4608 if (tdsConnection != null) {
4609 var parser = tdsConnection.Parser;
4610 if ((parser == null) || (parser.State == TdsParserState.Closed)) {
4611 throw ADP.OpenConnectionRequired(method, ConnectionState.Closed);
4613 else if (parser.State != TdsParserState.OpenLoggedIn) {
4614 throw ADP.OpenConnectionRequired(method, ConnectionState.Broken);
4617 else if (_activeConnection.State == ConnectionState.Closed) {
4618 throw ADP.OpenConnectionRequired(method, ConnectionState.Closed);
4620 else if (_activeConnection.State == ConnectionState.Broken) {
4621 throw ADP.OpenConnectionRequired(method, ConnectionState.Broken);
4624 ValidateAsyncCommand();
4626 TdsParser bestEffortCleanupTarget = null;
4627 RuntimeHelpers.PrepareConstrainedRegions();
4630 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
4632 RuntimeHelpers.PrepareConstrainedRegions();
4634 tdsReliabilitySection.Start();
4638 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
4639 // close any non MARS dead readers, if applicable, and then throw if still busy.
4640 // Throw if we have a live reader on this command
4641 _activeConnection.ValidateConnectionForExecute(method, this);
4646 tdsReliabilitySection.Stop();
4650 catch (System.OutOfMemoryException e)
4652 _activeConnection.Abort(e);
4655 catch (System.StackOverflowException e)
4657 _activeConnection.Abort(e);
4660 catch (System.Threading.ThreadAbortException e)
4662 _activeConnection.Abort(e);
4663 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
4666 // Check to see if the currently set transaction has completed. If so,
4667 // null out our local reference.
4668 if (null != _transaction && _transaction.Connection == null)
4669 _transaction = null;
4671 // throw if the connection is in a transaction but there is no
4672 // locally assigned transaction object
4673 if (_activeConnection.HasLocalTransactionFromAPI && (null == _transaction))
4674 throw ADP.TransactionRequired(method);
4676 // if we have a transaction, check to ensure that the active
4677 // connection property matches the connection associated with
4679 if (null != _transaction && _activeConnection != _transaction.Connection)
4680 throw ADP.TransactionConnectionMismatch();
4682 if (ADP.IsEmpty(this.CommandText))
4683 throw ADP.CommandTextRequired(method);
4685 // Notification property must be null for pre-Yukon connections
4686 if ((Notification != null) && !_activeConnection.IsYukonOrNewer) {
4687 throw SQL.NotificationsRequireYukon();
4690 if ((async) && (_activeConnection.IsContextConnection)) {
4691 // Async not supported on Context Connections
4692 throw SQL.NotAvailableOnContextConnection();
4696 private void ValidateAsyncCommand() {
4698 if (cachedAsyncState.PendingAsyncOperation) { // Enforce only one pending async execute at a time.
4699 if (cachedAsyncState.IsActiveConnectionValid(_activeConnection)) {
4700 throw SQL.PendingBeginXXXExists();
4703 _stateObj = null; // Session was re-claimed by session pool upon connection close.
4704 cachedAsyncState.ResetAsyncState();
4709 private void GetStateObject(TdsParser parser = null) {
4710 Debug.Assert (null == _stateObj,"StateObject not null on GetStateObject");
4711 Debug.Assert (null != _activeConnection, "no active connection?");
4713 if (_pendingCancel) {
4714 _pendingCancel = false; // Not really needed, but we'll reset anyways.
4716 // If a pendingCancel exists on the object, we must have had a Cancel() call
4717 // between the point that we entered an Execute* API and the point in Execute* that
4718 // we proceeded to call this function and obtain a stateObject. In that case,
4719 // we now throw a cancelled error.
4720 throw SQL.OperationCancelled();
4723 if (parser == null) {
4724 parser = _activeConnection.Parser;
4725 if ((parser == null) || (parser.State == TdsParserState.Broken) || (parser.State == TdsParserState.Closed)) {
4726 // Connection's parser is null as well, therefore we must be closed
4727 throw ADP.ClosedConnectionError();
4731 TdsParserStateObject stateObj = parser.GetSession(this);
4732 stateObj.StartSession(ObjectID);
4734 _stateObj = stateObj;
4736 if (_pendingCancel) {
4737 _pendingCancel = false; // Not really needed, but we'll reset anyways.
4739 // If a pendingCancel exists on the object, we must have had a Cancel() call
4740 // between the point that we entered this function and the point where we obtained
4741 // and actually assigned the stateObject to the local member. It is possible
4742 // that the flag is set as well as a call to stateObj.Cancel - though that would
4743 // be a no-op. So - throw.
4744 throw SQL.OperationCancelled();
4748 private void ReliablePutStateObject() {
4749 TdsParser bestEffortCleanupTarget = null;
4750 RuntimeHelpers.PrepareConstrainedRegions();
4753 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
4755 RuntimeHelpers.PrepareConstrainedRegions();
4757 tdsReliabilitySection.Start();
4761 bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
4767 tdsReliabilitySection.Stop();
4771 catch (System.OutOfMemoryException e)
4773 _activeConnection.Abort(e);
4776 catch (System.StackOverflowException e)
4778 _activeConnection.Abort(e);
4781 catch (System.Threading.ThreadAbortException e)
4783 _activeConnection.Abort(e);
4784 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
4789 private void PutStateObject() {
4790 TdsParserStateObject stateObj = _stateObj;
4793 if (null != stateObj) {
4794 stateObj.CloseSession();
4799 /// IMPORTANT NOTE: This is created as a copy of OnDoneProc below for Transparent Column Encryption improvement
4800 /// as there is not much time, to address regressions. Will revisit removing the duplication, when we have time again.
4802 internal void OnDoneDescribeParameterEncryptionProc(TdsParserStateObject stateObj) {
4803 // called per rpc batch complete
4805 // track the records affected for the just completed rpc batch
4806 // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches
4807 _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].cumulativeRecordsAffected = _rowsAffected;
4809 _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].recordsAffected =
4810 (((0 < _currentlyExecutingDescribeParameterEncryptionRPC) && (0 <= _rowsAffected))
4811 ? (_rowsAffected - Math.Max(_sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC - 1].cumulativeRecordsAffected, 0))
4814 // track the error collection (not available from TdsParser after ExecuteNonQuery)
4815 // and the which errors are associated with the just completed rpc batch
4816 _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].errorsIndexStart =
4817 ((0 < _currentlyExecutingDescribeParameterEncryptionRPC)
4818 ? _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC - 1].errorsIndexEnd
4820 _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].errorsIndexEnd = stateObj.ErrorCount;
4821 _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].errors = stateObj._errors;
4823 // track the warning collection (not available from TdsParser after ExecuteNonQuery)
4824 // and the which warnings are associated with the just completed rpc batch
4825 _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].warningsIndexStart =
4826 ((0 < _currentlyExecutingDescribeParameterEncryptionRPC)
4827 ? _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC - 1].warningsIndexEnd
4829 _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].warningsIndexEnd = stateObj.WarningCount;
4830 _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].warnings = stateObj._warnings;
4832 _currentlyExecutingDescribeParameterEncryptionRPC++;
4837 /// IMPORTANT NOTE: There is a copy of this function above in OnDoneDescribeParameterEncryptionProc.
4838 /// Please consider the changes being done in this function for the above function as well.
4840 internal void OnDoneProc() { // called per rpc batch complete
4843 // track the records affected for the just completed rpc batch
4844 // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches
4845 _SqlRPCBatchArray[_currentlyExecutingBatch].cumulativeRecordsAffected = _rowsAffected;
4847 _SqlRPCBatchArray[_currentlyExecutingBatch].recordsAffected =
4848 (((0 < _currentlyExecutingBatch) && (0 <= _rowsAffected))
4849 ? (_rowsAffected - Math.Max(_SqlRPCBatchArray[_currentlyExecutingBatch-1].cumulativeRecordsAffected, 0))
4852 // track the error collection (not available from TdsParser after ExecuteNonQuery)
4853 // and the which errors are associated with the just completed rpc batch
4854 _SqlRPCBatchArray[_currentlyExecutingBatch].errorsIndexStart =
4855 ((0 < _currentlyExecutingBatch)
4856 ? _SqlRPCBatchArray[_currentlyExecutingBatch-1].errorsIndexEnd
4858 _SqlRPCBatchArray[_currentlyExecutingBatch].errorsIndexEnd = _stateObj.ErrorCount;
4859 _SqlRPCBatchArray[_currentlyExecutingBatch].errors = _stateObj._errors;
4861 // track the warning collection (not available from TdsParser after ExecuteNonQuery)
4862 // and the which warnings are associated with the just completed rpc batch
4863 _SqlRPCBatchArray[_currentlyExecutingBatch].warningsIndexStart =
4864 ((0 < _currentlyExecutingBatch)
4865 ? _SqlRPCBatchArray[_currentlyExecutingBatch-1].warningsIndexEnd
4867 _SqlRPCBatchArray[_currentlyExecutingBatch].warningsIndexEnd = _stateObj.WarningCount;
4868 _SqlRPCBatchArray[_currentlyExecutingBatch].warnings = _stateObj._warnings;
4870 _currentlyExecutingBatch++;
4871 Debug.Assert(_parameterCollectionList.Count >= _currentlyExecutingBatch, "OnDoneProc: Too many DONEPROC events");
4879 internal void OnReturnStatus(int status) {
4883 // Don't set the return status if this is the status for sp_describe_parameter_encryption.
4884 if (IsDescribeParameterEncryptionRPCCurrentlyInProgress)
4887 SqlParameterCollection parameters = _parameters;
4889 if (_parameterCollectionList.Count > _currentlyExecutingBatch) {
4890 parameters = _parameterCollectionList[_currentlyExecutingBatch];
4893 Debug.Assert(false, "OnReturnStatus: SqlCommand got too many DONEPROC events");
4897 // see if a return value is bound
4898 int count = GetParameterCount(parameters);
4899 for (int i = 0; i < count; i++) {
4900 SqlParameter parameter = parameters[i];
4901 if (parameter.Direction == ParameterDirection.ReturnValue) {
4902 object v = parameter.Value;
4904 // if the user bound a sqlint32 (the only valid one for status, use it)
4905 if ( (null != v) && (v.GetType() == typeof(SqlInt32)) ) {
4906 parameter.Value = new SqlInt32(status); // value type
4909 parameter.Value = status;
4913 // If we are not in Batch RPC mode, update the query cache with the encryption MD.
4914 // We can do this now that we have distinguished between ReturnValue and ReturnStatus.
4915 // Read comment in AddQueryMetadata() for more details.
4916 if (!BatchRPCMode && CachingQueryMetadataPostponed) {
4917 SqlQueryMetadataCache.GetInstance().AddQueryMetadata(this, ignoreQueriesWithReturnValueParams: false);
4926 // Move the return value to the corresponding output parameter.
4927 // Return parameters are sent in the order in which they were defined in the procedure.
4928 // If named, match the parameter name, otherwise fill in based on ordinal position.
4929 // If the parameter is not bound, then ignore the return value.
4931 internal void OnReturnValue(SqlReturnValue rec, TdsParserStateObject stateObj) {
4934 if (!rec.value.IsNull) {
4935 _prepareHandle = rec.value.Int32;
4941 SqlParameterCollection parameters = GetCurrentParameterCollection();
4942 int count = GetParameterCount(parameters);
4945 SqlParameter thisParam = GetParameterForOutputValueExtraction(parameters, rec.parameter, count);
4947 if (null != thisParam) {
4948 // If the parameter's direction is InputOutput, Output or ReturnValue and it needs to be transparently encrypted/decrypted
4949 // then simply decrypt, deserialize and set the value.
4950 if (rec.cipherMD != null &&
4951 thisParam.CipherMetadata != null &&
4952 (thisParam.Direction == ParameterDirection.Output ||
4953 thisParam.Direction == ParameterDirection.InputOutput ||
4954 thisParam.Direction == ParameterDirection.ReturnValue)) {
4955 if(rec.tdsType != TdsEnums.SQLBIGVARBINARY) {
4956 throw SQL.InvalidDataTypeForEncryptedParameter(thisParam.ParameterNameFixed, rec.tdsType, TdsEnums.SQLBIGVARBINARY);
4959 // Decrypt the ciphertext
4960 TdsParser parser = _activeConnection.Parser;
4961 if ((parser == null) || (parser.State == TdsParserState.Closed) || (parser.State == TdsParserState.Broken)) {
4962 throw ADP.ClosedConnectionError();
4965 if (!rec.value.IsNull) {
4967 Debug.Assert(_activeConnection != null, @"_activeConnection should not be null");
4969 // Get the key information from the parameter and decrypt the value.
4970 rec.cipherMD.EncryptionInfo = thisParam.CipherMetadata.EncryptionInfo;
4971 byte[] unencryptedBytes = SqlSecurityUtility.DecryptWithKey(rec.value.ByteArray, rec.cipherMD, _activeConnection.DataSource);
4973 if (unencryptedBytes != null) {
4974 // Denormalize the value and convert it to the parameter type.
4975 SqlBuffer buffer = new SqlBuffer();
4976 parser.DeserializeUnencryptedValue(buffer, unencryptedBytes, rec, stateObj, rec.NormalizationRuleVersion);
4977 thisParam.SetSqlBuffer(buffer);
4980 catch (Exception e) {
4981 throw SQL.ParamDecryptionFailed(thisParam.ParameterNameFixed, null, e);
4985 // Create a new SqlBuffer and set it to null
4986 // Note: We can't reuse the SqlBuffer in "rec" below since it's already been set (to varbinary)
4987 // in previous call to TryProcessReturnValue().
4988 // Note 2: We will be coming down this code path only if the Command Setting is set to use TCE.
4989 // We pass the command setting as TCE enabled in the below call for this reason.
4990 SqlBuffer buff = new SqlBuffer();
4991 TdsParser.GetNullSqlValue(buff, rec, SqlCommandColumnEncryptionSetting.Enabled, parser.Connection);
4992 thisParam.SetSqlBuffer(buff);
4998 // if the value user has supplied a SqlType class, then just copy over the SqlType, otherwise convert
5000 object val = thisParam.Value;
5002 //set the UDT value as typed object rather than bytes
5003 if (SqlDbType.Udt == thisParam.SqlDbType) {
5006 Connection.CheckGetExtendedUDTInfo(rec, true);
5008 //extract the byte array from the param value
5009 if (rec.value.IsNull)
5010 data = DBNull.Value;
5012 data = rec.value.ByteArray; //should work for both sql and non-sql values
5015 //call the connection to instantiate the UDT object
5016 thisParam.Value = Connection.GetUdtValue(data, rec, false);
5018 catch (FileNotFoundException e) {
5020 // Assign Assembly.Load failure in case where assembly not on client.
5021 // This allows execution to complete and failure on SqlParameter.Value.
5022 thisParam.SetUdtLoadError(e);
5024 catch (FileLoadException e) {
5026 // Assign Assembly.Load failure in case where assembly cannot be loaded on client.
5027 // This allows execution to complete and failure on SqlParameter.Value.
5028 thisParam.SetUdtLoadError(e);
5033 thisParam.SetSqlBuffer(rec.value);
5036 MetaType mt = MetaType.GetMetaTypeFromSqlDbType(rec.type, rec.isMultiValued);
5038 if (rec.type == SqlDbType.Decimal) {
5039 thisParam.ScaleInternal = rec.scale;
5040 thisParam.PrecisionInternal = rec.precision;
5042 else if (mt.IsVarTime) {
5043 thisParam.ScaleInternal = rec.scale;
5045 else if (rec.type == SqlDbType.Xml) {
5046 SqlCachedBuffer cachedBuffer = (thisParam.Value as SqlCachedBuffer);
5047 if (null != cachedBuffer) {
5048 thisParam.Value = cachedBuffer.ToString();
5052 if (rec.collation != null) {
5053 Debug.Assert(mt.IsCharType, "Invalid collation structure for non-char type");
5054 thisParam.Collation = rec.collation;
5062 internal void OnParametersAvailableSmi( SmiParameterMetaData[] paramMetaData, ITypedGettersV3 parameterValues ) {
5063 Debug.Assert(null != paramMetaData);
5065 for(int index=0; index < paramMetaData.Length; index++) {
5066 OnParameterAvailableSmi(paramMetaData[index], parameterValues, index);
5070 internal void OnParameterAvailableSmi(SmiParameterMetaData metaData, ITypedGettersV3 parameterValues, int ordinal) {
5071 if ( ParameterDirection.Input != metaData.Direction ) {
5073 if (ParameterDirection.ReturnValue != metaData.Direction) {
5074 name = metaData.Name;
5077 SqlParameterCollection parameters = GetCurrentParameterCollection();
5078 int count = GetParameterCount(parameters);
5079 SqlParameter param = GetParameterForOutputValueExtraction(parameters, name, count);
5081 if ( null != param ) {
5082 param.LocaleId = (int)metaData.LocaleId;
5083 param.CompareInfo = metaData.CompareOptions;
5084 SqlBuffer buffer = new SqlBuffer();
5086 if (_activeConnection.IsKatmaiOrNewer) {
5087 result = ValueUtilsSmi.GetOutputParameterV200Smi(
5088 OutParamEventSink, (SmiTypedGetterSetter)parameterValues, ordinal, metaData, _smiRequestContext, buffer );
5091 result = ValueUtilsSmi.GetOutputParameterV3Smi(
5092 OutParamEventSink, parameterValues, ordinal, metaData, _smiRequestContext, buffer );
5094 if ( null != result ) {
5095 param.Value = result;
5098 param.SetSqlBuffer( buffer );
5104 private SqlParameterCollection GetCurrentParameterCollection() {
5106 if (_parameterCollectionList.Count > _currentlyExecutingBatch) {
5107 return _parameterCollectionList[_currentlyExecutingBatch];
5110 Debug.Assert(false, "OnReturnValue: SqlCommand got too many DONEPROC events");
5119 private SqlParameter GetParameterForOutputValueExtraction( SqlParameterCollection parameters,
5120 string paramName, int paramCount ) {
5121 SqlParameter thisParam = null;
5122 bool foundParam = false;
5124 if (null == paramName) {
5125 // rec.parameter should only be null for a return value from a function
5126 for (int i = 0; i < paramCount; i++) {
5127 thisParam = parameters[i];
5128 // searching for ReturnValue
5129 if (thisParam.Direction == ParameterDirection.ReturnValue) {
5136 for (int i = 0; i < paramCount; i++) {
5137 thisParam = parameters[i];
5138 // searching for Output or InputOutput or ReturnValue with matching name
5139 if (thisParam.Direction != ParameterDirection.Input && thisParam.Direction != ParameterDirection.ReturnValue && paramName == thisParam.ParameterNameFixed) {
5151 private void GetRPCObject(int paramCount, ref _SqlRPC rpc, bool forSpDescribeParameterEncryption = false) {
5152 // Designed to minimize necessary allocations
5155 if (!forSpDescribeParameterEncryption) {
5156 if (_rpcArrayOf1 == null) {
5157 _rpcArrayOf1 = new _SqlRPC[1];
5158 _rpcArrayOf1[0] = new _SqlRPC();
5161 rpc = _rpcArrayOf1[0];
5164 if (_rpcForEncryption == null) {
5165 _rpcForEncryption = new _SqlRPC();
5168 rpc = _rpcForEncryption;
5176 rpc.recordsAffected = default(int?);
5177 rpc.cumulativeRecordsAffected = -1;
5179 rpc.errorsIndexStart = 0;
5180 rpc.errorsIndexEnd = 0;
5183 rpc.warningsIndexStart = 0;
5184 rpc.warningsIndexEnd = 0;
5185 rpc.warnings = null;
5186 rpc.needsFetchParameterEncryptionMetadata = false;
5188 // Make sure there is enough space in the parameters and paramoptions arrays
5189 if(rpc.parameters == null || rpc.parameters.Length < paramCount) {
5190 rpc.parameters = new SqlParameter[paramCount];
5192 else if (rpc.parameters.Length > paramCount) {
5193 rpc.parameters[paramCount]=null; // Terminator
5195 if(rpc.paramoptions == null || (rpc.paramoptions.Length < paramCount)) {
5196 rpc.paramoptions = new byte[paramCount];
5199 for (ii = 0 ; ii < paramCount ; ii++)
5200 rpc.paramoptions[ii] = 0;
5204 private void SetUpRPCParameters (_SqlRPC rpc, int startCount, bool inSchema, SqlParameterCollection parameters) {
5206 int paramCount = GetParameterCount(parameters) ;
5208 TdsParser parser = _activeConnection.Parser;
5209 bool yukonOrNewer = parser.IsYukonOrNewer;
5211 for (ii = 0; ii < paramCount; ii++) {
5212 SqlParameter parameter = parameters[ii];
5213 parameter.Validate(ii, CommandType.StoredProcedure == CommandType);
5215 // func will change type to that with a 4 byte length if the type has a two
5216 // byte length and a parameter length > than that expressable in 2 bytes
5217 if ((!parameter.ValidateTypeLengths(yukonOrNewer).IsPlp) && (parameter.Direction != ParameterDirection.Output)) {
5218 parameter.FixStreamDataForNonPLP();
5221 if (ShouldSendParameter(parameter)) {
5222 rpc.parameters[j] = parameter;
5225 if (parameter.Direction == ParameterDirection.InputOutput ||
5226 parameter.Direction == ParameterDirection.Output)
5227 rpc.paramoptions[j] = TdsEnums.RPC_PARAM_BYREF;
5229 // Set the encryped bit, if the parameter is to be encrypted.
5230 if (parameter.CipherMetadata != null) {
5231 rpc.paramoptions[j] |= TdsEnums.RPC_PARAM_ENCRYPTED;
5234 // set default value bit
5235 if (parameter.Direction != ParameterDirection.Output) {
5236 // remember that null == Convert.IsEmpty, DBNull.Value is a database null!
5238 // MDAC 62117, don't assume a default value exists for parameters in the case when
5239 // the user is simply requesting schema
5240 // SQLBUVSTS 179488 TVPs use DEFAULT and do not allow NULL, even for schema only.
5241 if (null == parameter.Value && (!inSchema || SqlDbType.Structured == parameter.SqlDbType)) {
5242 rpc.paramoptions[j] |= TdsEnums.RPC_PARAM_DEFAULT;
5246 // Must set parameter option bit for LOB_COOKIE if unfilled LazyMat blob
5255 // prototype for sp_prepexec is:
5256 // sp_prepexec(@handle int IN/OUT, @batch_params ntext, @batch_text ntext, param1value,param2value...)
5258 private _SqlRPC BuildPrepExec(CommandBehavior behavior) {
5259 Debug.Assert(System.Data.CommandType.Text == this.CommandType, "invalid use of sp_prepexec for stored proc invocation!");
5260 SqlParameter sqlParam;
5263 int count = CountSendableParameters(_parameters);
5266 GetRPCObject(count + j, ref rpc);
5268 rpc.ProcID = TdsEnums.RPC_PROCID_PREPEXEC;
5269 rpc.rpcName = TdsEnums.SP_PREPEXEC;
5272 sqlParam = new SqlParameter(null, SqlDbType.Int);
5273 sqlParam.Direction = ParameterDirection.InputOutput;
5274 sqlParam.Value = _prepareHandle;
5275 rpc.parameters[0] = sqlParam;
5276 rpc.paramoptions[0] = TdsEnums.RPC_PARAM_BYREF;
5279 string paramList = BuildParamList(_stateObj.Parser, _parameters);
5280 sqlParam = new SqlParameter(null, ((paramList.Length<<1)<=TdsEnums.TYPE_SIZE_LIMIT)?SqlDbType.NVarChar:SqlDbType.NText, paramList.Length);
5281 sqlParam.Value = paramList;
5282 rpc.parameters[1] = sqlParam;
5285 string text = GetCommandText(behavior);
5286 sqlParam = new SqlParameter(null, ((text.Length<<1)<=TdsEnums.TYPE_SIZE_LIMIT)?SqlDbType.NVarChar:SqlDbType.NText, text.Length);
5287 sqlParam.Value = text;
5288 rpc.parameters[2] = sqlParam;
5290 SetUpRPCParameters (rpc, j, false, _parameters);
5296 // returns true if the parameter is not a return value
5297 // and it's value is not DBNull (for a nullable parameter)
5299 private static bool ShouldSendParameter(SqlParameter p, bool includeReturnValue = false) {
5300 switch (p.Direction) {
5301 case ParameterDirection.ReturnValue:
5302 // return value parameters are not sent, except for the parameter list of sp_describe_parameter_encryption
5303 return includeReturnValue;
5304 case ParameterDirection.Output:
5305 case ParameterDirection.InputOutput:
5306 case ParameterDirection.Input:
5307 // InputOutput/Output parameters are aways sent
5310 Debug.Assert(false, "Invalid ParameterDirection!");
5315 private int CountSendableParameters(SqlParameterCollection parameters) {
5318 if (parameters != null) {
5319 int count = parameters.Count;
5320 for (int i = 0; i < count; i++) {
5321 if (ShouldSendParameter(parameters[i]))
5328 // Returns total number of parameters
5329 private int GetParameterCount(SqlParameterCollection parameters) {
5330 return ((null != parameters) ? parameters.Count : 0);
5334 // build the RPC record header for this stored proc and add parameters
5336 private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _SqlRPC rpc) {
5337 Debug.Assert(this.CommandType == System.Data.CommandType.StoredProcedure, "Command must be a stored proc to execute an RPC");
5338 int count = CountSendableParameters(parameters);
5339 GetRPCObject(count, ref rpc);
5341 rpc.rpcName = this.CommandText; // just get the raw command text
5343 SetUpRPCParameters ( rpc, 0, inSchema, parameters);
5347 // build the RPC record header for sp_unprepare
5349 // prototype for sp_unprepare is:
5350 // sp_unprepare(@handle)
5353 private _SqlRPC BuildUnprepare() {
5354 Debug.Assert(_prepareHandle != 0, "Invalid call to sp_unprepare without a valid handle!");
5357 GetRPCObject(1, ref rpc);
5358 SqlParameter sqlParam;
5360 rpc.ProcID = TdsEnums.RPC_PROCID_UNPREPARE;
5361 rpc.rpcName = TdsEnums.SP_UNPREPARE;
5364 sqlParam = new SqlParameter(null, SqlDbType.Int);
5365 sqlParam.Value = _prepareHandle;
5366 rpc.parameters[0] = sqlParam;
5372 // build the RPC record header for sp_execute
5374 // prototype for sp_execute is:
5375 // sp_execute(@handle int,param1value,param2value...)
5377 private _SqlRPC BuildExecute(bool inSchema) {
5378 Debug.Assert(_prepareHandle != -1, "Invalid call to sp_execute without a valid handle!");
5381 int count = CountSendableParameters(_parameters);
5384 GetRPCObject(count + j, ref rpc);
5386 SqlParameter sqlParam;
5388 rpc.ProcID = TdsEnums.RPC_PROCID_EXECUTE;
5389 rpc.rpcName = TdsEnums.SP_EXECUTE;
5392 sqlParam = new SqlParameter(null, SqlDbType.Int);
5393 sqlParam.Value = _prepareHandle;
5394 rpc.parameters[0] = sqlParam;
5396 SetUpRPCParameters (rpc, j, inSchema, _parameters);
5401 // build the RPC record header for sp_executesql and add the parameters
5403 // prototype for sp_executesql is:
5404 // sp_executesql(@batch_text nvarchar(4000),@batch_params nvarchar(4000), param1,.. paramN)
5405 private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlParameterCollection parameters, ref _SqlRPC rpc) {
5407 Debug.Assert(_prepareHandle == -1, "This command has an existing handle, use sp_execute!");
5408 Debug.Assert(System.Data.CommandType.Text == this.CommandType, "invalid use of sp_executesql for stored proc invocation!");
5410 SqlParameter sqlParam;
5412 int cParams = CountSendableParameters(parameters);
5420 GetRPCObject(cParams + j, ref rpc);
5421 rpc.ProcID = TdsEnums.RPC_PROCID_EXECUTESQL;
5422 rpc.rpcName = TdsEnums.SP_EXECUTESQL;
5425 if (commandText == null) {
5426 commandText = GetCommandText(behavior);
5428 sqlParam = new SqlParameter(null, ((commandText.Length<<1)<=TdsEnums.TYPE_SIZE_LIMIT)?SqlDbType.NVarChar:SqlDbType.NText, commandText.Length);
5429 sqlParam.Value = commandText;
5430 rpc.parameters[0] = sqlParam;
5433 string paramList = BuildParamList(_stateObj.Parser, BatchRPCMode ? parameters : _parameters);
5434 sqlParam = new SqlParameter(null, ((paramList.Length<<1)<=TdsEnums.TYPE_SIZE_LIMIT)?SqlDbType.NVarChar:SqlDbType.NText, paramList.Length);
5435 sqlParam.Value = paramList;
5436 rpc.parameters[1] = sqlParam;
5438 bool inSchema = (0 != (behavior & CommandBehavior.SchemaOnly));
5439 SetUpRPCParameters (rpc, j, inSchema, parameters);
5444 /// This function constructs a string parameter containing the exec statement in the following format
5445 /// N'EXEC sp_name @param1=@param1, @param1=@param2, ..., @paramN=@paramN'
5451 private SqlParameter BuildStoredProcedureStatementForColumnEncryption(string storedProcedureName, SqlParameter[] parameters) {
5452 Debug.Assert(CommandType == CommandType.StoredProcedure, "BuildStoredProcedureStatementForColumnEncryption() should only be called for stored procedures");
5453 Debug.Assert(!string.IsNullOrWhiteSpace(storedProcedureName), "storedProcedureName cannot be null or empty in BuildStoredProcedureStatementForColumnEncryption");
5454 Debug.Assert(parameters != null, "parameters cannot be null in BuildStoredProcedureStatementForColumnEncryption");
5456 StringBuilder execStatement = new StringBuilder();
5457 execStatement.Append(@"EXEC ");
5459 // Find the return value parameter (if any).
5460 SqlParameter returnValueParameter = null;
5461 foreach (SqlParameter parameter in parameters) {
5462 if (parameter.Direction == ParameterDirection.ReturnValue) {
5463 returnValueParameter = parameter;
5468 // If there is a return value parameter we need to assign the result to it.
5469 // EXEC @returnValue = moduleName [parameters]
5470 if (returnValueParameter != null) {
5471 execStatement.AppendFormat(@"{0}=", returnValueParameter.ParameterNameFixed);
5474 execStatement.Append(ParseAndQuoteIdentifier(storedProcedureName, false));
5476 // Build parameter list in the format
5477 // @param1=@param1, @param1=@param2, ..., @paramn=@paramn
5479 // Append the first parameter
5482 if(parameters.Count() > 0) {
5483 // Skip the return value parameters.
5484 while (i < parameters.Count() && parameters[i].Direction == ParameterDirection.ReturnValue) {
5488 if (i < parameters.Count()) {
5489 // Possibility of a SQL Injection issue through parameter names and how to construct valid identifier for parameters.
5490 // Since the parameters comes from application itself, there should not be a security vulnerability.
5491 // Also since the query is not executed, but only analyzed there is no possibility for elevation of priviledge, but only for
5492 // incorrect results which would only affect the user that attempts the injection.
5493 execStatement.AppendFormat(@" {0}={0}", parameters[i].ParameterNameFixed);
5495 // InputOutput and Output parameters need to be marked as such.
5496 if (parameters[i].Direction == ParameterDirection.Output ||
5497 parameters[i].Direction == ParameterDirection.InputOutput) {
5498 execStatement.AppendFormat(@" OUTPUT");
5503 // Move to the next parameter.
5506 // Append the rest of parameters
5507 for (; i < parameters.Count(); i++) {
5508 if (parameters[i].Direction != ParameterDirection.ReturnValue) {
5509 execStatement.AppendFormat(@", {0}={0}", parameters[i].ParameterNameFixed);
5511 // InputOutput and Output parameters need to be marked as such.
5512 if (parameters[i].Direction == ParameterDirection.Output ||
5513 parameters[i].Direction == ParameterDirection.InputOutput) {
5514 execStatement.AppendFormat(@" OUTPUT");
5519 // Construct @tsql SqlParameter to be returned
5520 SqlParameter tsqlParameter = new SqlParameter(null, ((execStatement.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, execStatement.Length);
5521 tsqlParameter.Value = execStatement.ToString();
5523 return tsqlParameter;
5526 // paramList parameter for sp_executesql, sp_prepare, and sp_prepexec
5527 internal string BuildParamList(TdsParser parser, SqlParameterCollection parameters, bool includeReturnValue = false) {
5528 StringBuilder paramList = new StringBuilder();
5529 bool fAddSeperator = false;
5531 bool yukonOrNewer = parser.IsYukonOrNewer;
5535 count = parameters.Count;
5536 for (int i = 0; i < count; i++) {
5537 SqlParameter sqlParam = parameters[i];
5538 sqlParam.Validate(i, CommandType.StoredProcedure == CommandType);
5539 // skip ReturnValue parameters; we never send them to the server
5540 if (!ShouldSendParameter(sqlParam, includeReturnValue))
5543 // add our separator for the ith parmeter
5545 paramList.Append(',');
5547 paramList.Append(sqlParam.ParameterNameFixed);
5549 MetaType mt = sqlParam.InternalMetaType;
5551 //for UDTs, get the actual type name. Get only the typename, omitt catalog and schema names.
5552 //in TSQL you should only specify the unqualified type name
5554 // paragraph above doesn't seem to be correct. Server won't find the type
5555 // if we don't provide a fully qualified name
5556 paramList.Append(" ");
5557 if (mt.SqlDbType == SqlDbType.Udt) {
5558 string fullTypeName = sqlParam.UdtTypeName;
5559 if(ADP.IsEmpty(fullTypeName))
5560 throw SQL.MustSetUdtTypeNameForUdtParams();
5561 // DEVNOTE: do we need to escape the full type name?
5562 paramList.Append(ParseAndQuoteIdentifier(fullTypeName, true /* is UdtTypeName */));
5564 else if (mt.SqlDbType == SqlDbType.Structured) {
5565 string typeName = sqlParam.TypeName;
5566 if (ADP.IsEmpty(typeName)) {
5567 throw SQL.MustSetTypeNameForParam(mt.TypeName, sqlParam.ParameterNameFixed);
5569 paramList.Append(ParseAndQuoteIdentifier(typeName, false /* is not UdtTypeName*/));
5571 // TVPs currently are the only Structured type and must be read only, so add that keyword
5572 paramList.Append(" READONLY");
5575 // func will change type to that with a 4 byte length if the type has a two
5576 // byte length and a parameter length > than that expressable in 2 bytes
5577 mt = sqlParam.ValidateTypeLengths(yukonOrNewer);
5578 if ((!mt.IsPlp) && (sqlParam.Direction != ParameterDirection.Output)) {
5579 sqlParam.FixStreamDataForNonPLP();
5581 paramList.Append(mt.TypeName);
5584 fAddSeperator = true;
5586 if (mt.SqlDbType == SqlDbType.Decimal) {
5587 byte precision = sqlParam.GetActualPrecision();
5588 byte scale = sqlParam.GetActualScale();
5590 paramList.Append('(');
5592 if (0 == precision) {
5594 precision = TdsEnums.DEFAULT_NUMERIC_PRECISION;
5596 precision = TdsEnums.SPHINX_DEFAULT_NUMERIC_PRECISION;
5600 paramList.Append(precision);
5601 paramList.Append(',');
5602 paramList.Append(scale);
5603 paramList.Append(')');
5605 else if (mt.IsVarTime) {
5606 byte scale = sqlParam.GetActualScale();
5608 paramList.Append('(');
5609 paramList.Append(scale);
5610 paramList.Append(')');
5612 else if (false == mt.IsFixed && false == mt.IsLong && mt.SqlDbType != SqlDbType.Timestamp && mt.SqlDbType != SqlDbType.Udt && SqlDbType.Structured != mt.SqlDbType) {
5613 int size = sqlParam.Size;
5615 paramList.Append('(');
5617 // if using non unicode types, obtain the actual byte length from the parser, with it's associated code page
5618 if (mt.IsAnsiType) {
5619 object val = sqlParam.GetCoercedValue();
5622 // deal with the sql types
5623 if ((null != val) && (DBNull.Value != val)) {
5624 s = (val as string);
5626 SqlString sval = val is SqlString ? (SqlString)val : SqlString.Null;
5634 int actualBytes = parser.GetEncodingCharLength(s, sqlParam.GetActualSize(), sqlParam.Offset, null);
5635 // if actual number of bytes is greater than the user given number of chars, use actual bytes
5636 if (actualBytes > size)
5641 // bug 49497, if the user specifies a 0-sized parameter for a variable len field
5642 // pass over max size (8000 bytes or 4000 characters for wide types)
5644 size = mt.IsSizeInCharacters ? (TdsEnums.MAXSIZE >> 1) : TdsEnums.MAXSIZE;
5646 paramList.Append(size);
5647 paramList.Append(')');
5649 else if (mt.IsPlp && (mt.SqlDbType != SqlDbType.Xml) && (mt.SqlDbType != SqlDbType.Udt)) {
5650 paramList.Append("(max) ");
5653 // set the output bit for Output or InputOutput parameters
5654 if (sqlParam.Direction != ParameterDirection.Input)
5655 paramList.Append(" " + TdsEnums.PARAM_OUTPUT);
5658 return paramList.ToString();
5661 // Adds quotes to each part of a SQL identifier that may be multi-part, while leaving
5662 // the result as a single composite name.
5663 private string ParseAndQuoteIdentifier(string identifier, bool isUdtTypeName) {
5664 string[] strings = SqlParameter.ParseTypeName(identifier, isUdtTypeName);
5665 StringBuilder bld = new StringBuilder();
5667 // Stitching back together is a little tricky. Assume we want to build a full multi-part name
5668 // with all parts except trimming separators for leading empty names (null or empty strings,
5669 // but not whitespace). Separators in the middle should be added, even if the name part is
5670 // null/empty, to maintain proper location of the parts.
5671 for (int i = 0; i < strings.Length; i++ ) {
5672 if (0 < bld.Length) {
5675 if (null != strings[i] && 0 != strings[i].Length) {
5676 bld.Append(ADP.BuildQuotedString("[", "]", strings[i]));
5680 return bld.ToString();
5683 // returns set option text to turn on format only and key info on and off
5684 // @devnote: When we are executing as a text command, then we never need
5685 // to turn off the options since they command text is executed in the scope of sp_executesql.
5686 // For a stored proc command, however, we must send over batch sql and then turn off
5687 // the set options after we read the data. See the code in Command.Execute()
5688 private string GetSetOptionsString(CommandBehavior behavior) {
5691 if ((System.Data.CommandBehavior.SchemaOnly == (behavior & CommandBehavior.SchemaOnly)) ||
5692 (System.Data.CommandBehavior.KeyInfo == (behavior & CommandBehavior.KeyInfo))) {
5694 // MDAC 56898 - SET FMTONLY ON will cause the server to ignore other SET OPTIONS, so turn
5695 // it off before we ask for browse mode metadata
5696 s = TdsEnums.FMTONLY_OFF;
5698 if (System.Data.CommandBehavior.KeyInfo == (behavior & CommandBehavior.KeyInfo)) {
5699 s = s + TdsEnums.BROWSE_ON;
5702 if (System.Data.CommandBehavior.SchemaOnly == (behavior & CommandBehavior.SchemaOnly)) {
5703 s = s + TdsEnums.FMTONLY_ON;
5710 private string GetResetOptionsString(CommandBehavior behavior) {
5713 // SET FMTONLY ON OFF
5714 if (System.Data.CommandBehavior.SchemaOnly == (behavior & CommandBehavior.SchemaOnly)) {
5715 s = s + TdsEnums.FMTONLY_OFF;
5718 // SET NO_BROWSETABLE OFF
5719 if (System.Data.CommandBehavior.KeyInfo == (behavior & CommandBehavior.KeyInfo)) {
5720 s = s + TdsEnums.BROWSE_OFF;
5726 private String GetCommandText(CommandBehavior behavior) {
5727 // build the batch string we send over, since we execute within a stored proc (sp_executesql), the SET options never need to be
5728 // turned off since they are scoped to the sproc
5729 Debug.Assert(System.Data.CommandType.Text == this.CommandType, "invalid call to GetCommandText for stored proc!");
5730 return GetSetOptionsString(behavior) + this.CommandText;
5734 // build the RPC record header for sp_executesql and add the parameters
5736 // the prototype for sp_prepare is:
5737 // sp_prepare(@handle int OUTPUT, @batch_params ntext, @batch_text ntext, @options int default 0x1)
5738 private _SqlRPC BuildPrepare(CommandBehavior behavior) {
5739 Debug.Assert(System.Data.CommandType.Text == this.CommandType, "invalid use of sp_prepare for stored proc invocation!");
5742 GetRPCObject(3, ref rpc);
5743 SqlParameter sqlParam;
5745 rpc.ProcID = TdsEnums.RPC_PROCID_PREPARE;
5746 rpc.rpcName = TdsEnums.SP_PREPARE;
5749 sqlParam = new SqlParameter(null, SqlDbType.Int);
5750 sqlParam.Direction = ParameterDirection.Output;
5751 rpc.parameters[0] = sqlParam;
5752 rpc.paramoptions[0] = TdsEnums.RPC_PARAM_BYREF;
5755 string paramList = BuildParamList(_stateObj.Parser, _parameters);
5756 sqlParam = new SqlParameter(null, ((paramList.Length<<1)<=TdsEnums.TYPE_SIZE_LIMIT)?SqlDbType.NVarChar:SqlDbType.NText, paramList.Length);
5757 sqlParam.Value = paramList;
5758 rpc.parameters[1] = sqlParam;
5761 string text = GetCommandText(behavior);
5762 sqlParam = new SqlParameter(null, ((text.Length<<1)<=TdsEnums.TYPE_SIZE_LIMIT)?SqlDbType.NVarChar:SqlDbType.NText, text.Length);
5763 sqlParam.Value = text;
5764 rpc.parameters[2] = sqlParam;
5768 sqlParam = new SqlParameter(null, SqlDbType.Int);
5769 rpc.Parameters[3] = sqlParam;
5774 internal void CheckThrowSNIException() {
5775 var stateObj = _stateObj;
5776 if (stateObj != null) {
5777 stateObj.CheckThrowSNIException();
5781 // We're being notified that the underlying connection has closed
5782 internal void OnConnectionClosed() {
5784 var stateObj = _stateObj;
5785 if (stateObj != null) {
5786 stateObj.OnConnectionClosed();
5791 internal TdsParserStateObject StateObject {
5797 private bool IsPrepared {
5798 get { return(_execType != EXECTYPE.UNPREPARED);}
5801 private bool IsUserPrepared {
5802 get { return IsPrepared && !_hiddenPrepare && !IsDirty; }
5805 internal bool IsDirty {
5807 // only dirty if prepared
5808 var activeConnection = _activeConnection;
5809 return (IsPrepared &&
5811 ((_parameters != null) && (_parameters.IsDirty)) ||
5812 ((activeConnection != null) && ((activeConnection.CloseCount != _preparedConnectionCloseCount) || (activeConnection.ReconnectCount != _preparedConnectionReconnectCount)))));
5815 // only mark the command as dirty if it is already prepared
5816 // but always clear the value if it we are clearing the dirty flag
5817 _dirty = value ? IsPrepared : false;
5818 if (null != _parameters) {
5819 _parameters.IsDirty = _dirty;
5821 _cachedMetaData = null;
5826 /// Get or set the number of records affected by SpDescribeParameterEncryption.
5827 /// The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise.
5829 internal int RowsAffectedByDescribeParameterEncryption
5832 return _rowsAffectedBySpDescribeParameterEncryption;
5835 if (-1 == _rowsAffectedBySpDescribeParameterEncryption) {
5836 _rowsAffectedBySpDescribeParameterEncryption = value;
5838 else if (0 < value) {
5839 _rowsAffectedBySpDescribeParameterEncryption += value;
5844 internal int InternalRecordsAffected {
5846 return _rowsAffected;
5849 if (-1 == _rowsAffected) {
5850 _rowsAffected = value;
5852 else if (0 < value) {
5853 _rowsAffected += value;
5858 internal bool BatchRPCMode {
5860 return _batchRPCMode;
5863 _batchRPCMode = value;
5865 if (_batchRPCMode == false) {
5866 ClearBatchCommand();
5868 if (_RPCList == null) {
5869 _RPCList = new List<_SqlRPC>();
5871 if (_parameterCollectionList == null) {
5872 _parameterCollectionList = new List<SqlParameterCollection>();
5879 /// Clear the state in sqlcommand related to describe parameter encryption RPC requests.
5881 private void ClearDescribeParameterEncryptionRequests() {
5882 _sqlRPCParameterEncryptionReqArray = null;
5883 _currentlyExecutingDescribeParameterEncryptionRPC = 0;
5884 _isDescribeParameterEncryptionRPCCurrentlyInProgress = false;
5885 _rowsAffectedBySpDescribeParameterEncryption = -1;
5888 internal void ClearBatchCommand() {
5889 List<_SqlRPC> rpcList = _RPCList;
5890 if (null != rpcList) {
5893 if (null != _parameterCollectionList) {
5894 _parameterCollectionList.Clear();
5896 _SqlRPCBatchArray = null;
5897 _currentlyExecutingBatch = 0;
5901 /// Set the column encryption setting to the new one.
5902 /// Do not allow conflicting column encryption settings.
5904 private void SetColumnEncryptionSetting(SqlCommandColumnEncryptionSetting newColumnEncryptionSetting) {
5905 if (!this._wasBatchModeColumnEncryptionSettingSetOnce) {
5906 this._columnEncryptionSetting = newColumnEncryptionSetting;
5907 this._wasBatchModeColumnEncryptionSettingSetOnce = true;
5910 if (this._columnEncryptionSetting != newColumnEncryptionSetting) {
5911 throw SQL.BatchedUpdateColumnEncryptionSettingMismatch();
5916 internal void AddBatchCommand(string commandText, SqlParameterCollection parameters, CommandType cmdType, SqlCommandColumnEncryptionSetting columnEncryptionSetting) {
5917 Debug.Assert(BatchRPCMode, "Command is not in batch RPC Mode");
5918 Debug.Assert(_RPCList != null);
5919 Debug.Assert(_parameterCollectionList != null);
5921 _SqlRPC rpc = new _SqlRPC();
5923 this.CommandText = commandText;
5924 this.CommandType = cmdType;
5926 // Set the column encryption setting.
5927 SetColumnEncryptionSetting(columnEncryptionSetting);
5930 if (cmdType == CommandType.StoredProcedure) {
5931 BuildRPC(false, parameters, ref rpc);
5934 // All batch sql statements must be executed inside sp_executesql, including those without parameters
5935 BuildExecuteSql(CommandBehavior.Default, commandText, parameters, ref rpc);
5938 // Always add a parameters collection per RPC, even if there are no parameters.
5939 _parameterCollectionList.Add(parameters);
5941 ReliablePutStateObject();
5944 internal int ExecuteBatchRPCCommand() {
5946 Debug.Assert(BatchRPCMode, "Command is not in batch RPC Mode");
5947 Debug.Assert(_RPCList != null, "No batch commands specified");
5948 _SqlRPCBatchArray = _RPCList.ToArray();
5949 _currentlyExecutingBatch = 0;
5950 return ExecuteNonQuery(); // Check permissions, execute, return output params
5954 internal int? GetRecordsAffected(int commandIndex) {
5955 Debug.Assert(BatchRPCMode, "Command is not in batch RPC Mode");
5956 Debug.Assert(_SqlRPCBatchArray != null, "batch command have been cleared");
5957 return _SqlRPCBatchArray[commandIndex].recordsAffected;
5960 internal SqlException GetErrors(int commandIndex) {
5961 SqlException result = null;
5962 int length = (_SqlRPCBatchArray[commandIndex].errorsIndexEnd - _SqlRPCBatchArray[commandIndex].errorsIndexStart);
5964 SqlErrorCollection errors = new SqlErrorCollection();
5965 for(int i = _SqlRPCBatchArray[commandIndex].errorsIndexStart; i < _SqlRPCBatchArray[commandIndex].errorsIndexEnd; ++i) {
5966 errors.Add(_SqlRPCBatchArray[commandIndex].errors[i]);
5968 for(int i = _SqlRPCBatchArray[commandIndex].warningsIndexStart; i < _SqlRPCBatchArray[commandIndex].warningsIndexEnd; ++i) {
5969 errors.Add(_SqlRPCBatchArray[commandIndex].warnings[i]);
5971 result = SqlException.CreateException(errors, Connection.ServerVersion, Connection.ClientConnectionId);
5976 // Allocates and initializes a new SmiRequestExecutor based on the current command state
5977 private SmiRequestExecutor SetUpSmiRequest( SqlInternalConnectionSmi innerConnection ) {
5979 // General Approach To Ensure Security of Marshalling:
5980 // Only touch each item in the command once
5981 // (i.e. only grab a reference to each param once, only
5982 // read the type from that param once, etc.). The problem is
5983 // that if the user changes something on the command in the
5984 // middle of marshaling, it can overwrite the native buffers
5985 // set up. For example, if max length is used to allocate
5986 // buffers, but then re-read from the parameter to truncate
5987 // strings, the user could extend the length and overwrite
5990 if (null != Notification){
5991 throw SQL.NotificationsNotAvailableOnContextConnection();
5994 SmiParameterMetaData[] requestMetaData = null;
5995 ParameterPeekAheadValue[] peekAheadValues = null;
5997 // Length of rgMetadata becomes *the* official count of parameters to use,
5998 // don't rely on Parameters.Count after this point, as the user could change it.
5999 int count = GetParameterCount( Parameters );
6001 requestMetaData = new SmiParameterMetaData[count];
6002 peekAheadValues = new ParameterPeekAheadValue[count];
6004 // set up the metadata
6005 for ( int index=0; index<count; index++ ) {
6006 SqlParameter param = Parameters[index];
6007 param.Validate(index, CommandType.StoredProcedure == CommandType);
6008 requestMetaData[index] = param.MetaDataForSmi(out peekAheadValues[index]);
6010 // Check for valid type for version negotiated
6011 if (!innerConnection.IsKatmaiOrNewer) {
6012 MetaType mt = MetaType.GetMetaTypeFromSqlDbType(requestMetaData[index].SqlDbType, requestMetaData[index].IsMultiValued);
6013 if (!mt.Is90Supported) {
6014 throw ADP.VersionDoesNotSupportDataType(mt.TypeName);
6020 // Allocate the new request
6021 CommandType cmdType = CommandType;
6022 _smiRequestContext = innerConnection.InternalContext;
6023 SmiRequestExecutor requestExecutor = _smiRequestContext.CreateRequestExecutor(
6031 EventSink.ProcessMessagesAndThrow();
6033 // Now assign param values
6034 for ( int index=0; index<count; index++ ) {
6035 if ( ParameterDirection.Output != requestMetaData[index].Direction &&
6036 ParameterDirection.ReturnValue != requestMetaData[index].Direction ) {
6037 SqlParameter param = Parameters[index];
6038 // going back to command for parameter is ok, since we'll only pick up values now.
6039 object value = param.GetCoercedValue();
6040 if (value is XmlDataFeed && requestMetaData[index].SqlDbType != SqlDbType.Xml) {
6041 value = MetaType.GetStringFromXml(((XmlDataFeed)value)._source);
6043 ExtendedClrTypeCode typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType(requestMetaData[index].SqlDbType, requestMetaData[index].IsMultiValued, value, null /* parameters don't use CLR Type for UDTs */, SmiContextFactory.Instance.NegotiatedSmiVersion);
6045 // Handle null reference as special case for parameters
6046 if ( CommandType.StoredProcedure == cmdType &&
6047 ExtendedClrTypeCode.Empty == typeCode ) {
6048 requestExecutor.SetDefault( index );
6051 // SQLBU 402391 & 403631: Exception to prevent Parameter.Size data corruption cases from working.
6052 // This should be temporary until changing to correct behavior can be safely implemented.
6053 // initial size criteria is the same for all affected types
6054 // NOTE: assumes size < -1 is handled by SqlParameter.Size setter
6055 int size = param.Size;
6056 if (size != 0 && size != SmiMetaData.UnlimitedMaxLengthIndicator && !param.SizeInferred) {
6057 switch(requestMetaData[index].SqlDbType) {
6058 case SqlDbType.Image:
6059 case SqlDbType.Text:
6060 if (size != Int32.MaxValue) {
6061 throw SQL.ParameterSizeRestrictionFailure(index);
6065 case SqlDbType.NText:
6066 if (size != Int32.MaxValue/2) {
6067 throw SQL.ParameterSizeRestrictionFailure(index);
6071 case SqlDbType.VarBinary:
6072 case SqlDbType.VarChar:
6073 // Allow size==Int32.MaxValue because of DeriveParameters
6074 if (size > 0 && size != Int32.MaxValue && requestMetaData[index].MaxLength == SmiMetaData.UnlimitedMaxLengthIndicator) {
6075 throw SQL.ParameterSizeRestrictionFailure(index);
6079 case SqlDbType.NVarChar:
6080 // Allow size==Int32.MaxValue/2 because of DeriveParameters
6081 if (size > 0 && size != Int32.MaxValue/2 && requestMetaData[index].MaxLength == SmiMetaData.UnlimitedMaxLengthIndicator) {
6082 throw SQL.ParameterSizeRestrictionFailure(index);
6086 case SqlDbType.Timestamp:
6087 // Size limiting for larger values will happen due to MaxLength
6088 if (size < SmiMetaData.DefaultTimestamp.MaxLength) {
6089 throw SQL.ParameterSizeRestrictionFailure(index);
6093 case SqlDbType.Variant:
6094 // Variant problems happen when Size is less than maximums for character and binary values
6095 // Size limiting for larger values will happen due to MaxLength
6096 // NOTE: assumes xml and udt types are handled in parameter value coercion
6097 // since server does not allow these types in a variant
6098 if (null != value) {
6099 MetaType mt = MetaType.GetMetaTypeFromValue(value);
6101 if ((mt.IsNCharType && size < SmiMetaData.MaxUnicodeCharacters) ||
6102 (mt.IsBinType && size < SmiMetaData.MaxBinaryLength) ||
6103 (mt.IsAnsiType && size < SmiMetaData.MaxANSICharacters)) {
6104 throw SQL.ParameterSizeRestrictionFailure(index);
6110 // Xml is an issue for non-SqlXml types
6111 if (null != value && ExtendedClrTypeCode.SqlXml != typeCode) {
6112 throw SQL.ParameterSizeRestrictionFailure(index);
6116 // NOTE: Char, NChar, Binary and UDT do not need restricting because they are always 8k or less,
6117 // so the metadata MaxLength will match the Size setting.
6124 if (innerConnection.IsKatmaiOrNewer) {
6125 ValueUtilsSmi.SetCompatibleValueV200(EventSink, requestExecutor, index, requestMetaData[index], value, typeCode, param.Offset, param.Size, peekAheadValues[index]);
6128 ValueUtilsSmi.SetCompatibleValue( EventSink, requestExecutor, index, requestMetaData[index], value, typeCode, param.Offset );
6134 return requestExecutor;
6137 private void WriteBeginExecuteEvent()
6139 if (SqlEventSource.Log.IsEnabled() && Connection != null)
6141 string commandText = CommandType == CommandType.StoredProcedure ? CommandText : string.Empty;
6142 SqlEventSource.Log.BeginExecute(GetHashCode(), Connection.DataSource, Connection.Database, commandText);
6147 /// Writes and end execute event in Event Source.
6149 /// <param name="success">True if SQL command finished successfully, otherwise false.</param>
6150 /// <param name="sqlExceptionNumber">Gets a number that identifies the type of error.</param>
6151 /// <param name="synchronous">True if SQL command was executed synchronously, otherwise false.</param>
6152 private void WriteEndExecuteEvent(bool success, int? sqlExceptionNumber, bool synchronous)
6154 if (SqlEventSource.Log.IsEnabled())
6156 // SqlEventSource.WriteEvent(int, int, int, int) is faster than provided overload SqlEventSource.WriteEvent(int, object[]).
6157 // that's why trying to fit several booleans in one integer value
6159 // success state is stored the first bit in compositeState 0x01
6160 int successFlag = success ? 1 : 0;
6162 // isSqlException is stored in the 2nd bit in compositeState 0x100
6163 int isSqlExceptionFlag = sqlExceptionNumber.HasValue ? 2 : 0;
6165 // synchronous state is stored in the second bit in compositeState 0x10
6166 int synchronousFlag = synchronous ? 4 : 0;
6168 int compositeState = successFlag | isSqlExceptionFlag | synchronousFlag;
6170 SqlEventSource.Log.EndExecute(GetHashCode(), compositeState, sqlExceptionNumber.GetValueOrDefault());
6175 internal void CompletePendingReadWithSuccess(bool resetForcePendingReadsToWait) {
6176 var stateObj = _stateObj;
6177 if (stateObj != null) {
6178 stateObj.CompletePendingReadWithSuccess(resetForcePendingReadsToWait);
6181 var tempCachedAsyncState = cachedAsyncState;
6182 if (tempCachedAsyncState != null) {
6183 var reader = tempCachedAsyncState.CachedAsyncReader;
6184 if (reader != null) {
6185 reader.CompletePendingReadWithSuccess(resetForcePendingReadsToWait);
6191 internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendingReadsToWait) {
6192 var stateObj = _stateObj;
6193 if (stateObj != null) {
6194 stateObj.CompletePendingReadWithFailure(errorCode, resetForcePendingReadsToWait);
6197 var tempCachedAsyncState = _cachedAsyncState;
6198 if (tempCachedAsyncState != null) {
6199 var reader = tempCachedAsyncState.CachedAsyncReader;
6200 if (reader != null) {
6201 reader.CompletePendingReadWithFailure(errorCode, resetForcePendingReadsToWait);