diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs
index 1de6ca10b5..7a0491ad0f 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs
@@ -6,7 +6,6 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Data.SqlTypes;
@@ -16,11 +15,8 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
using Microsoft.Data.Common;
-using Microsoft.Data.Sql;
using Microsoft.Data.SqlClient.Diagnostics;
-using Microsoft.Data.SqlClient.Server;
// NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available.
// New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future.
@@ -246,72 +242,8 @@ private AsyncState CachedAsyncState
///
internal bool CachingQueryMetadataPostponed { get; set; }
- ///
- public SqlCommand() : base()
- {
- GC.SuppressFinalize(this);
- }
-
- ///
- public SqlCommand(string cmdText) : this()
- {
- CommandText = cmdText;
- }
-
- ///
- public SqlCommand(string cmdText, SqlConnection connection) : this()
- {
- CommandText = cmdText;
- Connection = connection;
- }
-
- ///
- public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction) : this()
- {
- CommandText = cmdText;
- Connection = connection;
- Transaction = transaction;
- }
-
- ///
- public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction, SqlCommandColumnEncryptionSetting columnEncryptionSetting) : this()
- {
- CommandText = cmdText;
- Connection = connection;
- Transaction = transaction;
- _columnEncryptionSetting = columnEncryptionSetting;
- }
-
- private SqlCommand(SqlCommand from) : this()
- {
- CommandText = from.CommandText;
- CommandTimeout = from.CommandTimeout;
- CommandType = from.CommandType;
- Connection = from.Connection;
- DesignTimeVisible = from.DesignTimeVisible;
- Transaction = from.Transaction;
- UpdatedRowSource = from.UpdatedRowSource;
- _columnEncryptionSetting = from.ColumnEncryptionSetting;
-
- SqlParameterCollection parameters = Parameters;
- foreach (object parameter in from.Parameters)
- {
- parameters.Add((parameter is ICloneable) ? (parameter as ICloneable).Clone() : parameter);
- }
- }
-
private bool IsProviderRetriable => SqlConfigurableRetryFactory.IsRetriable(RetryLogicProvider);
- ///
- public void ResetCommandTimeout()
- {
- if (ADP.DefaultCommandTimeout != CommandTimeout)
- {
- PropertyChanging();
- _commandTimeout = DefaultCommandTimeout;
- }
- }
-
internal void OnStatementCompleted(int recordCount)
{
if (0 <= recordCount)
@@ -335,125 +267,6 @@ internal void OnStatementCompleted(int recordCount)
}
}
- // Cancel is supposed to be multi-thread safe.
- // It doesn't make sense to verify the connection exists or that it is open during cancel
- // because immediately after checking the connection can be closed or removed via another thread.
- //
- ///
- public override void Cancel()
- {
- using (TryEventScope.Create("SqlCommand.Cancel | API | Object Id {0}", ObjectID))
- {
- SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlCommand.Cancel | API | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}, Command Text '{3}'", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId, CommandText);
-
- SqlStatistics statistics = null;
- try
- {
- statistics = SqlStatistics.StartTimer(Statistics);
-
- // If we are in reconnect phase simply cancel the waiting task
- var reconnectCompletionSource = _reconnectionCompletionSource;
- if (reconnectCompletionSource != null)
- {
- if (reconnectCompletionSource.TrySetCanceled())
- {
- return;
- }
- }
-
- // the pending data flag means that we are awaiting a response or are in the middle of processing a response
- // if we have no pending data, then there is nothing to cancel
- // if we have pending data, but it is not a result of this command, then we don't cancel either. Note that
- // this model is implementable because we only allow one active command at any one time. This code
- // will have to change we allow multiple outstanding batches
- if (_activeConnection == null)
- {
- return;
- }
- SqlInternalConnectionTds connection = (_activeConnection.InnerConnection as SqlInternalConnectionTds);
- if (connection == null)
- { // Fail with out locking
- return;
- }
-
- // The lock here is to protect against the command.cancel / connection.close race condition
- // The SqlInternalConnectionTds is set to OpenBusy during close, once this happens the cast below will fail and
- // the command will no longer be cancelable. It might be desirable to be able to cancel the close operation, but this is
- // outside of the scope of Whidbey RTM. See (SqlConnection::Close) for other lock.
- lock (connection)
- {
- 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 taking the lock, the connection has been closed
- return;
- }
-
- TdsParser parser = connection.Parser;
- if (parser == null)
- {
- return;
- }
-
- if (!_pendingCancel)
- {
- // Do nothing if already pending.
- // Before attempting actual cancel, set the _pendingCancel flag to false.
- // This denotes to other thread before obtaining stateObject from the
- // session pool that there is another thread wishing to cancel.
- // The period in question is between entering the ExecuteAPI and obtaining
- // a stateObject.
- _pendingCancel = true;
-
- TdsParserStateObject stateObj = _stateObj;
- if (stateObj != null)
- {
- stateObj.Cancel(this);
- }
- else
- {
- SqlDataReader reader = connection.FindLiveReader(this);
- if (reader != null)
- {
- reader.Cancel(this);
- }
- }
- }
- }
- }
- finally
- {
- SqlStatistics.StopTimer(statistics);
- }
- }
- }
-
- ///
- new public SqlParameter CreateParameter()
- {
- return new SqlParameter();
- }
-
- ///
- protected override DbParameter CreateDbParameter()
- {
- return CreateParameter();
- }
-
- ///
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- // release managed objects
- _cachedMetaData = null;
-
- // reset async cache information to allow a second async execute
- CachedAsyncState?.ResetAsyncState();
- }
- // release unmanaged objects
- base.Dispose(disposing);
- }
-
private void VerifyEndExecuteState(Task completionTask, string endMethod, bool fullCheckForColumnEncryption = false)
{
Debug.Assert(completionTask != null);
@@ -749,20 +562,6 @@ private EnclaveSessionParameters GetEnclaveSessionParameters()
this._activeConnection.Database);
}
- ///
- public void RegisterColumnEncryptionKeyStoreProvidersOnCommand(IDictionary customProviders)
- {
- ValidateCustomProviders(customProviders);
-
- // Create a temporary dictionary and then add items from the provided dictionary.
- // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs
- // in the provided customerProviders dictionary.
- Dictionary customColumnEncryptionKeyStoreProviders =
- new(customProviders, StringComparer.OrdinalIgnoreCase);
-
- _customColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders;
- }
-
private void ValidateCustomProviders(IDictionary customProviders)
{
// Throw when the provided dictionary is null.
@@ -1805,19 +1604,6 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(
}
}
- ///
- /// Constructs a SqlParameter with a given string value
- ///
- ///
- ///
- private SqlParameter GetSqlParameterWithQueryText(string queryText)
- {
- SqlParameter sqlParam = new SqlParameter(null, ((queryText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, queryText.Length);
- sqlParam.Value = queryText;
-
- return sqlParam;
- }
-
///
/// Constructs the sp_describe_parameter_encryption request with the values from the original RPC call.
/// Prototype for <sp_describe_parameter_encryption> is
@@ -2281,17 +2067,6 @@ private void ReadDescribeEncryptionParameterResults(
}
}
- ///
- public SqlCommand Clone()
- {
- SqlCommand clone = new SqlCommand(this);
- SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Clone | API | Object Id {0}, Clone Object Id {1}, Client Connection Id {2}", ObjectID, clone.ObjectID, Connection?.ClientConnectionId);
- return clone;
- }
-
- object ICloneable.Clone() =>
- Clone();
-
private Task RegisterForConnectionCloseNotification(Task outerTask)
{
SqlConnection connection = _activeConnection;
@@ -2457,273 +2232,6 @@ private void PutStateObject()
}
}
- internal void OnDoneDescribeParameterEncryptionProc(TdsParserStateObject stateObj)
- {
- // called per rpc batch complete
- if (_batchRPCMode)
- {
- OnDone(stateObj, _currentlyExecutingDescribeParameterEncryptionRPC, _sqlRPCParameterEncryptionReqArray, _rowsAffected);
- _currentlyExecutingDescribeParameterEncryptionRPC++;
- }
- }
-
- internal void OnDoneProc(TdsParserStateObject stateObject)
- {
- // called per rpc batch complete
- if (_batchRPCMode)
- {
- OnDone(stateObject, _currentlyExecutingBatch, _RPCList, _rowsAffected);
- _currentlyExecutingBatch++;
- Debug.Assert(_RPCList.Count >= _currentlyExecutingBatch, "OnDoneProc: Too many DONEPROC events");
- }
- }
-
- private static void OnDone(TdsParserStateObject stateObj, int index, IList<_SqlRPC> rpcList, int rowsAffected)
- {
- _SqlRPC current = rpcList[index];
- _SqlRPC previous = (index > 0) ? rpcList[index - 1] : null;
-
- // track the records affected for the just completed rpc batch
- // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches
- current.cumulativeRecordsAffected = rowsAffected;
-
- current.recordsAffected =
- (((previous != null) && (0 <= rowsAffected))
- ? (rowsAffected - Math.Max(previous.cumulativeRecordsAffected, 0))
- : rowsAffected);
-
- if (current.batchCommand != null)
- {
- current.batchCommand.SetRecordAffected(current.recordsAffected.GetValueOrDefault());
- }
-
- // track the error collection (not available from TdsParser after ExecuteNonQuery)
- // and the which errors are associated with the just completed rpc batch
- current.errorsIndexStart = previous?.errorsIndexEnd ?? 0;
- current.errorsIndexEnd = stateObj.ErrorCount;
- current.errors = stateObj._errors;
-
- // track the warning collection (not available from TdsParser after ExecuteNonQuery)
- // and the which warnings are associated with the just completed rpc batch
- current.warningsIndexStart = previous?.warningsIndexEnd ?? 0;
- current.warningsIndexEnd = stateObj.WarningCount;
- current.warnings = stateObj._warnings;
- }
-
- internal void OnReturnStatus(int status)
- {
- // Don't set the return status if this is the status for sp_describe_parameter_encryption.
- if (_inPrepare || IsDescribeParameterEncryptionRPCCurrentlyInProgress)
- {
- return;
- }
-
- SqlParameterCollection parameters = _parameters;
- if (_batchRPCMode)
- {
- if (_RPCList.Count > _currentlyExecutingBatch)
- {
- parameters = _RPCList[_currentlyExecutingBatch].userParams;
- }
- else
- {
- Debug.Fail("OnReturnStatus: SqlCommand got too many DONEPROC events");
- parameters = null;
- }
- }
- // see if a return value is bound
- int count = GetParameterCount(parameters);
- for (int i = 0; i < count; i++)
- {
- SqlParameter parameter = parameters[i];
- if (parameter.Direction == ParameterDirection.ReturnValue)
- {
- object v = parameter.Value;
-
- // if the user bound a sqlint32 (the only valid one for status, use it)
- if (v != null && (v.GetType() == typeof(SqlInt32)))
- {
- parameter.Value = new SqlInt32(status); // value type
- }
- else
- {
- parameter.Value = status;
- }
-
- // If we are not in Batch RPC mode, update the query cache with the encryption MD.
- // We can do this now that we have distinguished between ReturnValue and ReturnStatus.
- // Read comment in AddQueryMetadata() for more details.
- if (!_batchRPCMode && CachingQueryMetadataPostponed &&
- ShouldCacheEncryptionMetadata && (_parameters is not null && _parameters.Count > 0))
- {
- SqlQueryMetadataCache.GetInstance().AddQueryMetadata(this, ignoreQueriesWithReturnValueParams: false);
- }
-
- break;
- }
- }
- }
-
- //
- // Move the return value to the corresponding output parameter.
- // Return parameters are sent in the order in which they were defined in the procedure.
- // If named, match the parameter name, otherwise fill in based on ordinal position.
- // If the parameter is not bound, then ignore the return value.
- //
- internal void OnReturnValue(SqlReturnValue rec, TdsParserStateObject stateObj)
- {
- if (_inPrepare)
- {
- if (!rec.value.IsNull)
- {
- _prepareHandle = rec.value.Int32;
- }
- _inPrepare = false;
- return;
- }
-
- SqlParameterCollection parameters = GetCurrentParameterCollection();
- int count = GetParameterCount(parameters);
-
- SqlParameter thisParam = GetParameterForOutputValueExtraction(parameters, rec.parameter, count);
-
- if (thisParam != null)
- {
- // If the parameter's direction is InputOutput, Output or ReturnValue and it needs to be transparently encrypted/decrypted
- // then simply decrypt, deserialize and set the value.
- if (rec.cipherMD != null &&
- thisParam.CipherMetadata != null &&
- (thisParam.Direction == ParameterDirection.Output ||
- thisParam.Direction == ParameterDirection.InputOutput ||
- thisParam.Direction == ParameterDirection.ReturnValue))
- {
- if (rec.tdsType != TdsEnums.SQLBIGVARBINARY)
- {
- throw SQL.InvalidDataTypeForEncryptedParameter(thisParam.GetPrefixedParameterName(), rec.tdsType, TdsEnums.SQLBIGVARBINARY);
- }
-
- // Decrypt the ciphertext
- TdsParser parser = _activeConnection.Parser;
- if ((parser == null) || (parser.State == TdsParserState.Closed) || (parser.State == TdsParserState.Broken))
- {
- throw ADP.ClosedConnectionError();
- }
-
- if (!rec.value.IsNull)
- {
- try
- {
- Debug.Assert(_activeConnection != null, @"_activeConnection should not be null");
-
- // Get the key information from the parameter and decrypt the value.
- rec.cipherMD.EncryptionInfo = thisParam.CipherMetadata.EncryptionInfo;
- byte[] unencryptedBytes = SqlSecurityUtility.DecryptWithKey(rec.value.ByteArray, rec.cipherMD, _activeConnection, this);
-
- if (unencryptedBytes != null)
- {
- // Denormalize the value and convert it to the parameter type.
- SqlBuffer buffer = new SqlBuffer();
- parser.DeserializeUnencryptedValue(buffer, unencryptedBytes, rec, stateObj, rec.NormalizationRuleVersion);
- thisParam.SetSqlBuffer(buffer);
- }
- }
- catch (Exception e)
- {
- throw SQL.ParamDecryptionFailed(thisParam.GetPrefixedParameterName(), null, e);
- }
- }
- else
- {
- // Create a new SqlBuffer and set it to null
- // Note: We can't reuse the SqlBuffer in "rec" below since it's already been set (to varbinary)
- // in previous call to TryProcessReturnValue().
- // Note 2: We will be coming down this code path only if the Command Setting is set to use TCE.
- // We pass the command setting as TCE enabled in the below call for this reason.
- SqlBuffer buff = new SqlBuffer();
- TdsParser.GetNullSqlValue(buff, rec, SqlCommandColumnEncryptionSetting.Enabled, parser.Connection);
- thisParam.SetSqlBuffer(buff);
- }
- }
- else
- {
- // copy over data
-
- // if the value user has supplied a SqlType class, then just copy over the SqlType, otherwise convert
- // to the com type
- object val = thisParam.Value;
-
- //set the UDT value as typed object rather than bytes
- if (SqlDbType.Udt == thisParam.SqlDbType)
- {
- object data = null;
- try
- {
- Connection.CheckGetExtendedUDTInfo(rec, true);
-
- //extract the byte array from the param value
- if (rec.value.IsNull)
- {
- data = DBNull.Value;
- }
- else
- {
- data = rec.value.ByteArray; //should work for both sql and non-sql values
- }
-
- //call the connection to instantiate the UDT object
- thisParam.Value = Connection.GetUdtValue(data, rec, false);
- }
- catch (FileNotFoundException e)
- {
- // Assign Assembly.Load failure in case where assembly not on client.
- // This allows execution to complete and failure on SqlParameter.Value.
- thisParam.SetUdtLoadError(e);
- }
- catch (FileLoadException e)
- {
- // Assign Assembly.Load failure in case where assembly cannot be loaded on client.
- // This allows execution to complete and failure on SqlParameter.Value.
- thisParam.SetUdtLoadError(e);
- }
-
- return;
- }
- else
- {
- thisParam.SetSqlBuffer(rec.value);
- }
-
- MetaType mt = MetaType.GetMetaTypeFromSqlDbType(rec.type, false);
-
- if (rec.type == SqlDbType.Decimal)
- {
- thisParam.ScaleInternal = rec.scale;
- thisParam.PrecisionInternal = rec.precision;
- }
- else if (mt.IsVarTime)
- {
- thisParam.ScaleInternal = rec.scale;
- }
- else if (rec.type == SqlDbType.Xml)
- {
- SqlCachedBuffer cachedBuffer = (thisParam.Value as SqlCachedBuffer);
- if (cachedBuffer != null)
- {
- thisParam.Value = cachedBuffer.ToString();
- }
- }
-
- if (rec.collation != null)
- {
- Debug.Assert(mt.IsCharType, "Invalid collation structure for non-char type");
- thisParam.Collation = rec.collation;
- }
- }
- }
-
- return;
- }
-
private SqlParameterCollection GetCurrentParameterCollection()
{
if (_batchRPCMode)
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs
index 28e9b1ab60..68033e95cd 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Data.SqlTypes;
@@ -17,12 +16,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
-using System.Buffers;
using Microsoft.Data.Common;
-using Microsoft.Data.Sql;
-using Microsoft.Data.SqlClient.Server;
-using System.Transactions;
using System.Collections.Concurrent;
// NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available.
@@ -245,72 +239,8 @@ private AsyncState CachedAsyncState
///
internal bool CachingQueryMetadataPostponed { get; set; }
- ///
- public SqlCommand() : base()
- {
- GC.SuppressFinalize(this);
- }
-
- ///
- public SqlCommand(string cmdText) : this()
- {
- CommandText = cmdText;
- }
-
- ///
- public SqlCommand(string cmdText, SqlConnection connection) : this()
- {
- CommandText = cmdText;
- Connection = connection;
- }
-
- ///
- public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction) : this()
- {
- CommandText = cmdText;
- Connection = connection;
- Transaction = transaction;
- }
-
- ///
- public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction, SqlCommandColumnEncryptionSetting columnEncryptionSetting) : this()
- {
- CommandText = cmdText;
- Connection = connection;
- Transaction = transaction;
- _columnEncryptionSetting = columnEncryptionSetting;
- }
-
- private SqlCommand(SqlCommand from) : this()
- {
- CommandText = from.CommandText;
- CommandTimeout = from.CommandTimeout;
- CommandType = from.CommandType;
- Connection = from.Connection;
- DesignTimeVisible = from.DesignTimeVisible;
- Transaction = from.Transaction;
- UpdatedRowSource = from.UpdatedRowSource;
- _columnEncryptionSetting = from.ColumnEncryptionSetting;
-
- SqlParameterCollection parameters = Parameters;
- foreach (object parameter in from.Parameters)
- {
- parameters.Add((parameter is ICloneable) ? (parameter as ICloneable).Clone() : parameter);
- }
- }
-
private bool IsProviderRetriable => SqlConfigurableRetryFactory.IsRetriable(RetryLogicProvider);
- ///
- public void ResetCommandTimeout()
- {
- if (ADP.DefaultCommandTimeout != CommandTimeout)
- {
- PropertyChanging();
- _commandTimeout = DefaultCommandTimeout;
- }
- }
-
internal void OnStatementCompleted(int recordCount)
{
if (0 <= recordCount)
@@ -336,126 +266,6 @@ internal void OnStatementCompleted(int recordCount)
}
}
- // Cancel is supposed to be multi-thread safe.
- // It doesn't make sense to verify the connection exists or that it is open during cancel
- // because immediately after checkin the connection can be closed or removed via another thread.
- //
- ///
- public override void Cancel()
- {
- using (TryEventScope.Create("SqlCommand.Cancel | API | Object Id {0}", ObjectID))
- {
- SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlCommand.Cancel | API | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}, Command Text '{3}'", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId, CommandText);
-
- SqlStatistics statistics = null;
- try
- {
- statistics = SqlStatistics.StartTimer(Statistics);
-
- // If we are in reconnect phase simply cancel the waiting task
- var reconnectCompletionSource = _reconnectionCompletionSource;
- if (reconnectCompletionSource != null)
- {
- if (reconnectCompletionSource.TrySetCanceled())
- {
- return;
- }
- }
-
- // the pending data flag means that we are awaiting a response or are in the middle of processing a response
- // if we have no pending data, then there is nothing to cancel
- // if we have pending data, but it is not a result of this command, then we don't cancel either. Note that
- // this model is implementable because we only allow one active command at any one time. This code
- // will have to change we allow multiple outstanding batches
- if (_activeConnection == null)
- {
- return;
- }
- SqlInternalConnectionTds connection = (_activeConnection.InnerConnection as SqlInternalConnectionTds);
- if (connection == null)
- { // Fail with out locking
- return;
- }
-
- // The lock here is to protect against the command.cancel / connection.close race condition
- // The SqlInternalConnectionTds is set to OpenBusy during close, once this happens the cast below will fail and
- // the command will no longer be cancelable. It might be desirable to be able to cancel the close operation, but this is
- // outside of the scope of Whidbey RTM. See (SqlConnection::Close) for other lock.
- lock (connection)
- {
- 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 taking the lock, the connection has been closed
- return;
- }
-
- TdsParser parser = connection.Parser;
- if (parser == null)
- {
- return;
- }
-
- if (!_pendingCancel)
- {
- // Do nothing if aleady pending.
- // Before attempting actual cancel, set the _pendingCancel flag to false.
- // This denotes to other thread before obtaining stateObject from the
- // session pool that there is another thread wishing to cancel.
- // The period in question is between entering the ExecuteAPI and obtaining
- // a stateObject.
- _pendingCancel = true;
-
- TdsParserStateObject stateObj = _stateObj;
- if (stateObj != null)
- {
- stateObj.Cancel(this);
- }
- else
- {
- SqlDataReader reader = connection.FindLiveReader(this);
- if (reader != null)
- {
- reader.Cancel(this);
- }
- }
- }
- // @TODO: CER Exception Handling was removed here (see GH#3581)
- }
- }
- finally
- {
- SqlStatistics.StopTimer(statistics);
- }
- }
- }
-
- ///
- new public SqlParameter CreateParameter()
- {
- return new SqlParameter();
- }
-
- ///
- protected override DbParameter CreateDbParameter()
- {
- return CreateParameter();
- }
-
- ///
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- // release managed objects
- _cachedMetaData = null;
-
- // reset async cache information to allow a second async execute
- CachedAsyncState?.ResetAsyncState();
- }
- // release unmanaged objects
- base.Dispose(disposing);
- }
-
private void VerifyEndExecuteState(Task completionTask, string endMethod, bool fullCheckForColumnEncryption = false)
{
Debug.Assert(completionTask != null);
@@ -720,20 +530,6 @@ private EnclaveSessionParameters GetEnclaveSessionParameters()
this._activeConnection.Database);
}
- ///
- public void RegisterColumnEncryptionKeyStoreProvidersOnCommand(IDictionary customProviders)
- {
- ValidateCustomProviders(customProviders);
-
- // Create a temporary dictionary and then add items from the provided dictionary.
- // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs
- // in the provided customerProviders dictionary.
- Dictionary customColumnEncryptionKeyStoreProviders =
- new(customProviders, StringComparer.OrdinalIgnoreCase);
-
- _customColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders;
- }
-
private void ValidateCustomProviders(IDictionary customProviders)
{
// Throw when the provided dictionary is null.
@@ -1769,19 +1565,6 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(
}
}
- ///
- /// Constructs a SqlParameter with a given string value
- ///
- ///
- ///
- private SqlParameter GetSqlParameterWithQueryText(string queryText)
- {
- SqlParameter sqlParam = new SqlParameter(null, ((queryText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, queryText.Length);
- sqlParam.Value = queryText;
-
- return sqlParam;
- }
-
///
/// Constructs the sp_describe_parameter_encryption request with the values from the original RPC call.
/// Prototype for <sp_describe_parameter_encryption> is
@@ -2245,17 +2028,6 @@ private void ReadDescribeEncryptionParameterResults(
}
}
- ///
- public SqlCommand Clone()
- {
- SqlCommand clone = new SqlCommand(this);
- SqlClientEventSource.Log.TryTraceEvent("SqlCommand.Clone | API | Object Id {0}, Clone Object Id {1}, Client Connection Id {2}", ObjectID, clone.ObjectID, Connection?.ClientConnectionId);
- return clone;
- }
-
- object ICloneable.Clone() =>
- Clone();
-
private Task RegisterForConnectionCloseNotification(Task outterTask)
{
SqlConnection connection = _activeConnection;
@@ -2424,278 +2196,6 @@ private void PutStateObject()
}
}
- internal void OnDoneDescribeParameterEncryptionProc(TdsParserStateObject stateObj)
- {
- // called per rpc batch complete
- if (_batchRPCMode)
- {
- OnDone(stateObj, _currentlyExecutingDescribeParameterEncryptionRPC, _sqlRPCParameterEncryptionReqArray, _rowsAffected);
- _currentlyExecutingDescribeParameterEncryptionRPC++;
- }
- }
-
- internal void OnDoneProc(TdsParserStateObject stateObject)
- {
- // called per rpc batch complete
- if (_batchRPCMode)
- {
- OnDone(stateObject, _currentlyExecutingBatch, _RPCList, _rowsAffected);
- _currentlyExecutingBatch++;
- Debug.Assert(_RPCList.Count >= _currentlyExecutingBatch, "OnDoneProc: Too many DONEPROC events");
- }
- }
-
- private static void OnDone(TdsParserStateObject stateObj, int index, IList<_SqlRPC> rpcList, int rowsAffected)
- {
- _SqlRPC current = rpcList[index];
- _SqlRPC previous = (index > 0) ? rpcList[index - 1] : null;
-
- // track the records affected for the just completed rpc batch
- // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches
- current.cumulativeRecordsAffected = rowsAffected;
-
- current.recordsAffected =
- (((previous != null) && (0 <= rowsAffected))
- ? (rowsAffected - Math.Max(previous.cumulativeRecordsAffected, 0))
- : rowsAffected);
-
- if (current.batchCommand != null)
- {
- current.batchCommand.SetRecordAffected(current.recordsAffected.GetValueOrDefault());
- }
-
- // track the error collection (not available from TdsParser after ExecuteNonQuery)
- // and the which errors are associated with the just completed rpc batch
- current.errorsIndexStart = previous?.errorsIndexEnd ?? 0;
- current.errorsIndexEnd = stateObj.ErrorCount;
- current.errors = stateObj._errors;
-
- // track the warning collection (not available from TdsParser after ExecuteNonQuery)
- // and the which warnings are associated with the just completed rpc batch
- current.warningsIndexStart = previous?.warningsIndexEnd ?? 0;
- current.warningsIndexEnd = stateObj.WarningCount;
- current.warnings = stateObj._warnings;
- }
-
- internal void OnReturnStatus(int status)
- {
- if (_inPrepare)
- {
- return;
- }
-
- // Don't set the return status if this is the status for sp_describe_parameter_encryption.
- if (IsDescribeParameterEncryptionRPCCurrentlyInProgress)
- {
- return;
- }
-
- SqlParameterCollection parameters = _parameters;
- if (_batchRPCMode)
- {
- if (_RPCList.Count > _currentlyExecutingBatch)
- {
- parameters = _RPCList[_currentlyExecutingBatch].userParams;
- }
- else
- {
- Debug.Fail("OnReturnStatus: SqlCommand got too many DONEPROC events");
- parameters = null;
- }
- }
- // see if a return value is bound
- int count = GetParameterCount(parameters);
- for (int i = 0; i < count; i++)
- {
- SqlParameter parameter = parameters[i];
- if (parameter.Direction == ParameterDirection.ReturnValue)
- {
- object v = parameter.Value;
-
- // if the user bound a sqlint32 (the only valid one for status, use it)
- if (v != null && (v.GetType() == typeof(SqlInt32)))
- {
- parameter.Value = new SqlInt32(status); // value type
- }
- else
- {
- parameter.Value = status;
- }
-
- // If we are not in Batch RPC mode, update the query cache with the encryption MD.
- // We can do this now that we have distinguished between ReturnValue and ReturnStatus.
- // Read comment in AddQueryMetadata() for more details.
- if (!_batchRPCMode && CachingQueryMetadataPostponed &&
- ShouldCacheEncryptionMetadata && (_parameters is not null && _parameters.Count > 0))
- {
- SqlQueryMetadataCache.GetInstance().AddQueryMetadata(this, ignoreQueriesWithReturnValueParams: false);
- }
-
- break;
- }
- }
- }
-
- //
- // Move the return value to the corresponding output parameter.
- // Return parameters are sent in the order in which they were defined in the procedure.
- // If named, match the parameter name, otherwise fill in based on ordinal position.
- // If the parameter is not bound, then ignore the return value.
- //
- internal void OnReturnValue(SqlReturnValue rec, TdsParserStateObject stateObj)
- {
- if (_inPrepare)
- {
- if (!rec.value.IsNull)
- {
- _prepareHandle = rec.value.Int32;
- }
- _inPrepare = false;
- return;
- }
-
- SqlParameterCollection parameters = GetCurrentParameterCollection();
- int count = GetParameterCount(parameters);
-
- SqlParameter thisParam = GetParameterForOutputValueExtraction(parameters, rec.parameter, count);
-
- if (thisParam != null)
- {
- // If the parameter's direction is InputOutput, Output or ReturnValue and it needs to be transparently encrypted/decrypted
- // then simply decrypt, deserialize and set the value.
- if (rec.cipherMD != null &&
- thisParam.CipherMetadata != null &&
- (thisParam.Direction == ParameterDirection.Output ||
- thisParam.Direction == ParameterDirection.InputOutput ||
- thisParam.Direction == ParameterDirection.ReturnValue))
- {
- if (rec.tdsType != TdsEnums.SQLBIGVARBINARY)
- {
- throw SQL.InvalidDataTypeForEncryptedParameter(thisParam.GetPrefixedParameterName(), rec.tdsType, TdsEnums.SQLBIGVARBINARY);
- }
-
- // Decrypt the ciphertext
- TdsParser parser = _activeConnection.Parser;
- if ((parser == null) || (parser.State == TdsParserState.Closed) || (parser.State == TdsParserState.Broken))
- {
- throw ADP.ClosedConnectionError();
- }
-
- if (!rec.value.IsNull)
- {
- try
- {
- Debug.Assert(_activeConnection != null, @"_activeConnection should not be null");
-
- // Get the key information from the parameter and decrypt the value.
- rec.cipherMD.EncryptionInfo = thisParam.CipherMetadata.EncryptionInfo;
- byte[] unencryptedBytes = SqlSecurityUtility.DecryptWithKey(rec.value.ByteArray, rec.cipherMD, _activeConnection, this);
-
- if (unencryptedBytes != null)
- {
- // Denormalize the value and convert it to the parameter type.
- SqlBuffer buffer = new SqlBuffer();
- parser.DeserializeUnencryptedValue(buffer, unencryptedBytes, rec, stateObj, rec.NormalizationRuleVersion);
- thisParam.SetSqlBuffer(buffer);
- }
- }
- catch (Exception e)
- {
- throw SQL.ParamDecryptionFailed(thisParam.GetPrefixedParameterName(), null, e);
- }
- }
- else
- {
- // Create a new SqlBuffer and set it to null
- // Note: We can't reuse the SqlBuffer in "rec" below since it's already been set (to varbinary)
- // in previous call to TryProcessReturnValue().
- // Note 2: We will be coming down this code path only if the Command Setting is set to use TCE.
- // We pass the command setting as TCE enabled in the below call for this reason.
- SqlBuffer buff = new SqlBuffer();
- TdsParser.GetNullSqlValue(buff, rec, SqlCommandColumnEncryptionSetting.Enabled, parser.Connection);
- thisParam.SetSqlBuffer(buff);
- }
- }
- else
- {
- // copy over data
-
- // if the value user has supplied a SqlType class, then just copy over the SqlType, otherwise convert
- // to the com type
- object val = thisParam.Value;
-
- //set the UDT value as typed object rather than bytes
- if (SqlDbType.Udt == thisParam.SqlDbType)
- {
- object data = null;
- try
- {
- Connection.CheckGetExtendedUDTInfo(rec, true);
-
- //extract the byte array from the param value
- if (rec.value.IsNull)
- {
- data = DBNull.Value;
- }
- else
- {
- data = rec.value.ByteArray; //should work for both sql and non-sql values
- }
-
- //call the connection to instantiate the UDT object
- thisParam.Value = Connection.GetUdtValue(data, rec, false);
- }
- catch (FileNotFoundException e)
- {
- // Assign Assembly.Load failure in case where assembly not on client.
- // This allows execution to complete and failure on SqlParameter.Value.
- thisParam.SetUdtLoadError(e);
- }
- catch (FileLoadException e)
- {
- // Assign Assembly.Load failure in case where assembly cannot be loaded on client.
- // This allows execution to complete and failure on SqlParameter.Value.
- thisParam.SetUdtLoadError(e);
- }
-
- return;
- }
- else
- {
- thisParam.SetSqlBuffer(rec.value);
- }
-
- MetaType mt = MetaType.GetMetaTypeFromSqlDbType(rec.type, rec.IsMultiValued);
-
- if (rec.type == SqlDbType.Decimal)
- {
- thisParam.ScaleInternal = rec.scale;
- thisParam.PrecisionInternal = rec.precision;
- }
- else if (mt.IsVarTime)
- {
- thisParam.ScaleInternal = rec.scale;
- }
- else if (rec.type == SqlDbType.Xml)
- {
- SqlCachedBuffer cachedBuffer = (thisParam.Value as SqlCachedBuffer);
- if (cachedBuffer != null)
- {
- thisParam.Value = cachedBuffer.ToString();
- }
- }
-
- if (rec.collation != null)
- {
- Debug.Assert(mt.IsCharType, "Invalid collation structure for non-char type");
- thisParam.Collation = rec.collation;
- }
- }
- }
-
- return;
- }
-
private SqlParameterCollection GetCurrentParameterCollection()
{
if (_batchRPCMode)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs
index ef39945ea4..6fc39959cd 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs
@@ -3,10 +3,13 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
+using System.Data.SqlTypes;
using System.Diagnostics;
+using System.IO;
using System.Threading;
using Microsoft.Data.Common;
using Microsoft.Data.Sql;
@@ -195,6 +198,75 @@ public sealed partial class SqlCommand : DbCommand, ICloneable
#endregion
+ #region Constructors
+
+ ///
+ public SqlCommand()
+ {
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ public SqlCommand(string cmdText)
+ : this()
+ {
+ CommandText = cmdText;
+ }
+
+ ///
+ public SqlCommand(string cmdText, SqlConnection connection)
+ : this()
+ {
+ CommandText = cmdText;
+ Connection = connection;
+ }
+
+ ///
+ public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction)
+ : this()
+ {
+ CommandText = cmdText;
+ Connection = connection;
+ Transaction = transaction;
+ }
+
+ ///
+ public SqlCommand(
+ string cmdText,
+ SqlConnection connection,
+ SqlTransaction transaction,
+ SqlCommandColumnEncryptionSetting columnEncryptionSetting)
+ : this()
+ {
+ CommandText = cmdText;
+ Connection = connection;
+ Transaction = transaction;
+ _columnEncryptionSetting = columnEncryptionSetting;
+ }
+
+ private SqlCommand(SqlCommand from)
+ {
+ CommandText = from.CommandText;
+ CommandTimeout = from.CommandTimeout;
+ CommandType = from.CommandType;
+ Connection = from.Connection;
+ DesignTimeVisible = from.DesignTimeVisible;
+ Transaction = from.Transaction;
+ UpdatedRowSource = from.UpdatedRowSource;
+ _columnEncryptionSetting = from.ColumnEncryptionSetting;
+
+ SqlParameterCollection parameters = Parameters;
+ foreach (object parameter in from.Parameters)
+ {
+ object parameterToAdd = parameter is ICloneable cloneableParameter
+ ? cloneableParameter.Clone()
+ : parameter;
+ parameters.Add(parameterToAdd);
+ }
+ }
+
+ #endregion
+
#region Events
///
@@ -236,7 +308,7 @@ private enum EXECTYPE
}
#endregion
-
+
#region Public Properties
///
@@ -695,6 +767,122 @@ private bool IsDirty
#region Public/Internal Methods
+ object ICloneable.Clone() =>
+ Clone();
+
+ ///
+ public override void Cancel()
+ {
+ // Cancel is supposed to be multi-thread safe.
+ // It doesn't make sense to verify the connection exists or that it is open during
+ // cancel because immediately after checking the connection can be closed or removed
+ // via another thread.
+
+ using var eventScope = TryEventScope.Create($"SqlCommand.Cancel | API | Object Id {ObjectID}");
+ SqlClientEventSource.Log.TryCorrelationTraceEvent(
+ "SqlCommand.Cancel | API | Correlation | " +
+ $"Object Id {ObjectID}, " +
+ $"Activity Id {ActivityCorrelator.Current}, " +
+ $"Client Connection Id {_activeConnection.ClientConnectionId}, " +
+ $"Command Text '{CommandText}'");
+
+ SqlStatistics statistics = null;
+ try
+ {
+ statistics = SqlStatistics.StartTimer(Statistics);
+
+ // If we are in reconnect phase simply cancel the waiting task
+ var reconnectCompletionSource = _reconnectionCompletionSource;
+ if (reconnectCompletionSource is not null && reconnectCompletionSource.TrySetCanceled())
+ {
+ return;
+ }
+
+ // The pending data flag means that we are awaiting a response or are in the middle
+ // of processing a response.
+ // * If we have no pending data, then there is nothing to cancel.
+ // * If we have pending data, but it is not a result of this command, then we don't
+ // cancel either.
+ // Note that this model is implementable because we only allow one active command
+ // at any one time. This code will have to change we allow multiple outstanding
+ // batches.
+ if (_activeConnection?.InnerConnection is not SqlInternalConnectionTds connection)
+ {
+ // @TODO: Really this case only applies if the connection is null.
+ // Fail without locking
+ return;
+ }
+
+ // The lock here is to protect against the command.cancel / connection.close race
+ // condition. The SqlInternalConnectionTds is set to OpenBusy during close, once
+ // this happens the cast below will fail and the command will no longer be
+ // cancelable. It might be desirable to be able to cancel the close operation, but
+ // this is outside the scope of Whidbey RTM. See (SqlConnection::Close) for other lock.
+ lock (connection)
+ {
+ // Make sure the connection did not get changed getting the connection and
+ // taking the lock. If it has, the connection has been closed.
+ if (connection != _activeConnection.InnerConnection as SqlInternalConnectionTds)
+ {
+ return;
+ }
+
+ TdsParser parser = connection.Parser;
+ if (parser is null)
+ {
+ return;
+ }
+
+ if (!_pendingCancel)
+ {
+ // Do nothing if already pending.
+ // Before attempting actual cancel, set the _pendingCancel flag to false.
+ // This denotes to other thread before obtaining stateObject from the
+ // session pool that there is another thread wishing to cancel.
+ // The period in question is between entering the ExecuteAPI and obtaining
+ // a stateObject.
+ _pendingCancel = true;
+
+ TdsParserStateObject stateObj = _stateObj;
+ if (stateObj is not null)
+ {
+ stateObj.Cancel(this);
+ }
+ else
+ {
+ SqlDataReader reader = connection.FindLiveReader(this);
+ if (reader is not null)
+ {
+ reader.Cancel(this);
+ }
+ }
+ }
+ }
+ // @TODO: CER Exception Handling was removed here (see GH#3581)
+ }
+ finally
+ {
+ SqlStatistics.StopTimer(statistics);
+ }
+ }
+
+ ///
+ public SqlCommand Clone()
+ {
+ SqlCommand clone = new SqlCommand(this);
+ SqlClientEventSource.Log.TryTraceEvent(
+ "SqlCommand.Clone | API | " +
+ $"Object Id {ObjectID}, " +
+ $"Clone Object Id {clone.ObjectID}, " +
+ $"Client Connection Id {_activeConnection?.ClientConnectionId}");
+
+ return clone;
+ }
+
+ ///
+ public new SqlParameter CreateParameter() =>
+ new SqlParameter();
+
///
public override void Prepare()
{
@@ -774,11 +962,355 @@ public override void Prepare()
}
}
+ ///
+ public void RegisterColumnEncryptionKeyStoreProvidersOnCommand(
+ IDictionary customProviders)
+ {
+ ValidateCustomProviders(customProviders);
+
+ // Create a temporary dictionary and then add items from the provided dictionary.
+ // Dictionary constructor does shallow copying by simply copying the provider name and
+ // provider name and provider reference pairs.
+ Dictionary customColumnEncryptionKeyStoreProviders =
+ new(customProviders, StringComparer.OrdinalIgnoreCase);
+ _customColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders;
+ }
+
+ ///
+ public void ResetCommandTimeout()
+ {
+ if (CommandTimeout != ADP.DefaultCommandTimeout)
+ {
+ PropertyChanging();
+ _commandTimeout = DefaultCommandTimeout;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void OnDoneDescribeParameterEncryptionProc(TdsParserStateObject stateObj)
+ {
+ // @TODO: Is this not the same stateObj as the currently stored one?
+
+ // Called on RPC batch complete
+ if (_batchRPCMode)
+ {
+ OnDone(
+ stateObj,
+ index: _currentlyExecutingDescribeParameterEncryptionRPC,
+ rpcList: _sqlRPCParameterEncryptionReqArray,
+ _rowsAffected);
+ _currentlyExecutingDescribeParameterEncryptionRPC++; // @TODO: Should be interlocked?
+ }
+ }
+
+ internal void OnDoneProc(TdsParserStateObject stateObject)
+ {
+ // @TODO: Is this not the same stateObj as the currently stored one?
+
+ // Called on RPC batch complete
+ if (_batchRPCMode)
+ {
+ OnDone(stateObject, _currentlyExecutingBatch, _RPCList, _rowsAffected);
+ _currentlyExecutingBatch++; // @TODO: Should be interlocked?
+
+ Debug.Assert(_RPCList.Count >= _currentlyExecutingBatch, "OnDoneProc: Too many DONEPROC events");
+ }
+ }
+
+ internal void OnReturnStatus(int status)
+ {
+ // Don't set the return status if this is the status for sp_describe_parameter_encryption
+ if (_inPrepare || IsDescribeParameterEncryptionRPCCurrentlyInProgress)
+ {
+ return;
+ }
+
+ // @TODO: Replace with call to GetCurrentParameterCollection
+ SqlParameterCollection parameters = _parameters;
+ if (_batchRPCMode)
+ {
+ if (_RPCList.Count > _currentlyExecutingBatch)
+ {
+ parameters = _RPCList[_currentlyExecutingBatch].userParams;
+ }
+ else
+ {
+ Debug.Fail("OnReturnStatus: SqlCommand got too many DONEPROC events");
+ }
+ }
+
+ // See if a return value is bound
+ int count = GetParameterCount(parameters);
+ for (int i = 0; i < count; i++)
+ {
+ SqlParameter parameter = parameters[i];
+
+ // @TODO: Invert to reduce nesting :)
+ if (parameter.Direction is ParameterDirection.ReturnValue)
+ {
+ object value = parameter.Value;
+
+ // if the user bound a SqlInt32 (the only valid one for status) use it
+ // @TODO: Not sure if this can be converted to a ternary since that forces implicit conversion of status to SqlInt32
+ if (value is SqlInt32)
+ {
+ parameter.Value = new SqlInt32(status);
+ }
+ else
+ {
+ parameter.Value = status;
+ }
+
+ // If we are not in batch RPC mode, update the query cache with the encryption
+ // metadata. We can do this now if we have distinguished between ReturnValue
+ // and ReturnStatus.
+ // See comments in AddQueryMetadata() for more details.
+ if (!_batchRPCMode && CachingQueryMetadataPostponed && ShouldCacheEncryptionMetadata &&
+ _parameters?.Count > 0)
+ {
+ SqlQueryMetadataCache.GetInstance().AddQueryMetadata(
+ this,
+ ignoreQueriesWithReturnValueParams: false);
+ }
+ }
+ }
+ }
+
+ internal void OnReturnValue(SqlReturnValue returnValue, TdsParserStateObject stateObj)
+ {
+ // Move the return value to the corresponding output parameter.
+ // Return parameters are sent in the order in which they were defined in the procedure.
+ // If named, match the parameter name, otherwise fill in based on ordinal position. If
+ // the parameter is not bound, then ignore the return value.
+
+ // @TODO: Is stateObj supposed to be different than the currently stored state object?
+
+ if (_inPrepare)
+ {
+ // Store the returned prepare handle if we are returning from sp_prepare
+ if (!returnValue.value.IsNull)
+ {
+ _prepareHandle = returnValue.value.Int32;
+ }
+
+ _inPrepare = false;
+ return;
+ }
+
+ SqlParameterCollection parameters = GetCurrentParameterCollection();
+ int count = GetParameterCount(parameters);
+
+ // @TODO: Rename to "ReturnParam"
+ SqlParameter thisParam = GetParameterForOutputValueExtraction(parameters, returnValue.parameter, count);
+ if (thisParam is not null)
+ {
+ // @TODO: Invert to reduce nesting :)
+
+ // If the parameter's direction is InputOutput, Output, or ReturnValue and it needs
+ // to be transparently encrypted/decrypted, then simply decrypt, deserialize, and
+ // set the value.
+ if (returnValue.cipherMD is not null &&
+ thisParam.CipherMetadata is not null &&
+ (thisParam.Direction == ParameterDirection.Output ||
+ thisParam.Direction == ParameterDirection.InputOutput ||
+ thisParam.Direction == ParameterDirection.ReturnValue))
+ {
+ // @TODO: make this a separate method
+ // Validate type of the return value is valid for encryption
+ if (returnValue.tdsType != TdsEnums.SQLBIGVARBINARY)
+ {
+ throw SQL.InvalidDataTypeForEncryptedParameter(
+ thisParam.GetPrefixedParameterName(),
+ returnValue.tdsType,
+ expectedDataType: TdsEnums.SQLBIGVARBINARY);
+ }
+
+ // Decrypt the cipher text
+ TdsParser parser = _activeConnection.Parser;
+ if (parser is null || parser.State is TdsParserState.Closed or TdsParserState.Broken)
+ {
+ throw ADP.ClosedConnectionError();
+ }
+
+ if (!returnValue.value.IsNull)
+ {
+ try
+ {
+ Debug.Assert(_activeConnection is not null, @"_activeConnection should not be null");
+
+ // Get the key information from the parameter and decrypt the value.
+ returnValue.cipherMD.EncryptionInfo = thisParam.CipherMetadata.EncryptionInfo;
+ byte[] unencryptedBytes = SqlSecurityUtility.DecryptWithKey(
+ returnValue.value.ByteArray,
+ returnValue.cipherMD,
+ _activeConnection,
+ this);
+
+ if (unencryptedBytes is not null)
+ {
+ // Denormalize the value and convert it to the parameter type.
+ SqlBuffer buffer = new SqlBuffer();
+ parser.DeserializeUnencryptedValue(
+ buffer,
+ unencryptedBytes,
+ returnValue,
+ stateObj,
+ returnValue.NormalizationRuleVersion);
+ thisParam.SetSqlBuffer(buffer);
+ }
+ }
+ catch (Exception e)
+ {
+ throw SQL.ParamDecryptionFailed(
+ thisParam.GetPrefixedParameterName(),
+ serverName: null,
+ e);
+ }
+ }
+ else
+ {
+ // Create a new SqlBuffer and set it to null
+ // Note: We can't reuse the SqlBuffer in "returnValue" below since it's
+ // already been set (to varbinary) in previous call to
+ // TryProcessReturnValue().
+ // Note 2: We will be coming down this code path only if the Command
+ // Setting is set to use TCE. We pass the command setting as TCE enabled in
+ // the below call for this reason.
+ SqlBuffer buff = new SqlBuffer();
+ // @TODO: uhhh what? can't we just, idk, set it null in the buffer?
+ TdsParser.GetNullSqlValue(
+ buff,
+ returnValue,
+ SqlCommandColumnEncryptionSetting.Enabled,
+ parser.Connection);
+ thisParam.SetSqlBuffer(buff);
+ }
+ }
+ else
+ {
+ // @TODO: This should be a separate method, too
+ // Copy over the data
+
+ // If the value user has supplied a SqlType class, then just copy over the
+ // SqlType, otherwise convert to the com type.
+ if (thisParam.SqlDbType is SqlDbType.Udt)
+ {
+ try
+ {
+ _activeConnection.CheckGetExtendedUDTInfo(returnValue, fThrow: true);
+
+ // Extract the byte array from the param value
+ object data = returnValue.value.IsNull
+ ? DBNull.Value
+ : returnValue.value.ByteArray;
+
+ // Call the connection to instantiate the UDT object
+ thisParam.Value = _activeConnection.GetUdtValue(data, returnValue, returnDBNull: false);
+ }
+ catch (Exception e) when (e is FileNotFoundException or FileLoadException)
+ {
+ // Assign Assembly.Load failure in case where assembly not on client.
+ // This allows execution to complete and failure on SqlParameter.Value.
+ thisParam.SetUdtLoadError(e);
+ }
+
+ return;
+ }
+ else
+ {
+ thisParam.SetSqlBuffer(returnValue.value);
+ }
+
+ // @TODO: This seems fishy to me, it seems like it should be part of the SqlReturnValue class
+ MetaType mt = MetaType.GetMetaTypeFromSqlDbType(returnValue.type, isMultiValued: false);
+
+ if (returnValue.type is SqlDbType.Decimal)
+ {
+ thisParam.ScaleInternal = returnValue.scale;
+ thisParam.PrecisionInternal = returnValue.precision;
+ }
+ else if (mt.IsVarTime)
+ {
+ thisParam.ScaleInternal = returnValue.scale;
+ }
+ else if (returnValue.type is SqlDbType.Xml)
+ {
+ if (thisParam.Value is SqlCachedBuffer cachedBuffer)
+ {
+ thisParam.Value = cachedBuffer.ToString();
+ }
+ }
+
+ if (returnValue.collation is not null)
+ {
+ Debug.Assert(mt.IsCharType, "Invalid collation structure for non-char type");
+ thisParam.Collation = returnValue.collation;
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ ///
+ protected override DbParameter CreateDbParameter() =>
+ CreateParameter();
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // Release managed objects
+ _cachedMetaData = null;
+
+ // Reset async cache information to allow a second async execute
+ CachedAsyncState?.ResetAsyncState();
+ }
+
+ // Release unmanaged objects
+ base.Dispose(disposing);
+ }
+
#endregion
#region Private Methods
- // @TODO: Rename to PrepareInternal
+ private static void OnDone(TdsParserStateObject stateObj, int index, IList<_SqlRPC> rpcList, int rowsAffected)
+ {
+ // @TODO: Is the state object not the same as the currently stored one?
+
+ _SqlRPC current = rpcList[index];
+ _SqlRPC previous = index > 0 ? rpcList[index - 1] : null;
+
+ // Track the records affected for the just-completed RPC batch.
+ // _rowsAffected is cumulative for ExecuteNonQuery across all RPC batches
+ current.cumulativeRecordsAffected = rowsAffected;
+ current.recordsAffected = previous is not null && rowsAffected >= 0
+ ? rowsAffected - Math.Max(previous.cumulativeRecordsAffected, 0)
+ : rowsAffected;
+
+ current.batchCommand?.SetRecordAffected(current.recordsAffected.GetValueOrDefault());
+
+ // Track the error collection (not available from TdsParser after ExecuteNonQuery)
+ // and which errors are associated with the just-completed RPC batch.
+ current.errorsIndexStart = previous?.errorsIndexEnd ?? 0;
+ current.errorsIndexEnd = stateObj.ErrorCount;
+ current.errors = stateObj._errors;
+
+ // Track the warning collection (not available from TdsParser after ExecuteNonQuery)
+ // and which warnings are associated with the just-completed RPC batch.
+ current.warningsIndexStart = previous?.warningsIndexEnd ?? 0;
+ current.warningsIndexEnd = stateObj.WarningCount;
+ current.warnings = stateObj._warnings;
+ }
+
+ // @TODO: Rename PrepareInternal
private void InternalPrepare()
{
if (IsDirty)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs
index 964e46aca3..0af5ea44e6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs
@@ -37,6 +37,7 @@ private SqlQueryMetadataCache()
_cache = new MemoryCache(new MemoryCacheOptions());
}
+ // @TODO: Replace with Instance property.
internal static SqlQueryMetadataCache GetInstance()
{
return s_singletonInstance;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
index abceeb05b7..8f0bb915c0 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
@@ -656,6 +656,7 @@ internal SqlParameter GetParameterByIndex(int index, out byte options)
internal sealed class SqlReturnValue : SqlMetaDataPriv
{
+ // @TODO: Make auto properties (and rename to match)
internal string parameter;
internal readonly SqlBuffer value;
#if NETFRAMEWORK