diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
index 6ae23e11e6..0283736785 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
@@ -3374,7 +3374,7 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout,
{
// 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.
// So input parameters start at parameters[1]. parameters[0] is the actual T-SQL Statement. rpcName is sp_executesql.
- if (_SqlRPCBatchArray[i].parameters.Length > 1)
+ if (_SqlRPCBatchArray[i].systemParams.Length > 1)
{
_SqlRPCBatchArray[i].needsFetchParameterEncryptionMetadata = true;
@@ -3419,20 +3419,11 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout,
_sqlRPCParameterEncryptionReqArray = new _SqlRPC[1];
_SqlRPC rpc = null;
- GetRPCObject(GetParameterCount(_parameters), ref rpc);
+ GetRPCObject(0, GetParameterCount(_parameters), ref rpc);
Debug.Assert(rpc != null, "GetRPCObject should not return rpc as null.");
rpc.rpcName = CommandText;
-
- int i = 0;
-
- if (_parameters != null)
- {
- foreach (SqlParameter sqlParam in _parameters)
- {
- rpc.parameters[i++] = sqlParam;
- }
- }
+ rpc.userParams = _parameters;
// Prepare the RPC request for describe parameter encryption procedure.
PrepareDescribeParameterEncryptionRequest(rpc, ref _sqlRPCParameterEncryptionReqArray[0], serializedAttestatationParameters);
@@ -3482,20 +3473,23 @@ private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcReques
// Construct the RPC request for sp_describe_parameter_encryption
// sp_describe_parameter_encryption always has 2 parameters (stmt, paramlist).
// sp_describe_parameter_encryption can have an optional 3rd parameter (attestationParametes), used to identify and execute attestation protocol
- GetRPCObject(attestationParameters == null ? 2 : 3, ref describeParameterEncryptionRequest, forSpDescribeParameterEncryption: true);
+ GetRPCObject(attestationParameters == null ? 2 : 3, 0, ref describeParameterEncryptionRequest, forSpDescribeParameterEncryption: true);
describeParameterEncryptionRequest.rpcName = "sp_describe_parameter_encryption";
// Prepare @tsql parameter
- SqlParameter sqlParam;
string text;
// 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.
if (BatchRPCMode)
{
- Debug.Assert(originalRpcRequest.parameters != null && originalRpcRequest.parameters.Length > 0,
+ Debug.Assert(originalRpcRequest.systemParamCount > 0,
"originalRpcRequest didn't have at-least 1 parameter in BatchRPCMode, in PrepareDescribeParameterEncryptionRequest.");
- text = (string)originalRpcRequest.parameters[0].Value;
- sqlParam = GetSqlParameterWithQueryText(text);
+ text = (string)originalRpcRequest.systemParams[0].Value;
+ //@tsql
+ SqlParameter tsqlParam = describeParameterEncryptionRequest.systemParams[0];
+ tsqlParam.SqlDbType = ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
+ tsqlParam.Value = text;
+ tsqlParam.Size = text.Length;
}
else
{
@@ -3504,42 +3498,57 @@ private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcReques
{
// For stored procedures, we need to prepare @tsql in the following format
// N'EXEC sp_name @param1=@param1, @param1=@param2, ..., @paramN=@paramN'
- sqlParam = BuildStoredProcedureStatementForColumnEncryption(text, originalRpcRequest.parameters);
+ describeParameterEncryptionRequest.systemParams[0] = BuildStoredProcedureStatementForColumnEncryption(text, originalRpcRequest.userParams);
}
else
{
- sqlParam = GetSqlParameterWithQueryText(text);
+ //@tsql
+ SqlParameter tsqlParam = describeParameterEncryptionRequest.systemParams[0];
+ tsqlParam.SqlDbType = ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
+ tsqlParam.Value = text;
+ tsqlParam.Size = text.Length;
}
}
Debug.Assert(text != null, "@tsql parameter is null in PrepareDescribeParameterEncryptionRequest.");
-
- describeParameterEncryptionRequest.parameters[0] = sqlParam;
string parameterList = null;
// In BatchRPCMode, the input parameters start at parameters[1]. parameters[0] is the T-SQL statement. rpcName is sp_executesql.
// And it is already in the format expected out of BuildParamList, which is not the case with Non-BatchRPCMode.
if (BatchRPCMode)
{
- if (originalRpcRequest.parameters.Length > 1)
+ // systemParamCount == 2 when user parameters are supplied to BuildExcucuteSql
+ if (originalRpcRequest.systemParamCount > 1)
{
- parameterList = (string)originalRpcRequest.parameters[1].Value;
+ parameterList = (string)originalRpcRequest.systemParams[1].Value;
}
}
else
{
// Prepare @params parameter
// Need to create new parameters as we cannot have the same parameter being part of two SqlCommand objects
- SqlParameter paramCopy;
SqlParameterCollection tempCollection = new SqlParameterCollection();
- if (_parameters != null)
- {
- for (int i = 0; i < _parameters.Count; i++)
- {
- SqlParameter param = originalRpcRequest.parameters[i];
- paramCopy = new SqlParameter(param.ParameterName, param.SqlDbType, param.Size, param.Direction, param.Precision, param.Scale, param.SourceColumn, param.SourceVersion,
- param.SourceColumnNullMapping, param.Value, param.XmlSchemaCollectionDatabase, param.XmlSchemaCollectionOwningSchema, param.XmlSchemaCollectionName);
+ if (originalRpcRequest.userParams != null)
+ {
+ for (int i = 0; i < originalRpcRequest.userParams.Count; i++)
+ {
+ SqlParameter param = originalRpcRequest.userParams[i];
+ SqlParameter paramCopy = new SqlParameter(
+ param.ParameterName,
+ param.SqlDbType,
+ param.Size,
+ param.Direction,
+ param.Precision,
+ param.Scale,
+ param.SourceColumn,
+ param.SourceVersion,
+ param.SourceColumnNullMapping,
+ param.Value,
+ param.XmlSchemaCollectionDatabase,
+ param.XmlSchemaCollectionOwningSchema,
+ param.XmlSchemaCollectionName
+ );
paramCopy.CompareInfo = param.CompareInfo;
paramCopy.TypeName = param.TypeName;
paramCopy.UdtTypeName = param.UdtTypeName;
@@ -3568,20 +3577,19 @@ private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcReques
parameterList = BuildParamList(tdsParser, tempCollection, includeReturnValue: true);
}
- sqlParam = new SqlParameter(null, ((parameterList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, parameterList.Length);
- sqlParam.Value = parameterList;
- describeParameterEncryptionRequest.parameters[1] = sqlParam;
+ //@parameters
+
+ SqlParameter paramsParam = describeParameterEncryptionRequest.systemParams[1];
+ paramsParam.SqlDbType = ((parameterList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
+ paramsParam.Size = parameterList.Length;
+ paramsParam.Value = parameterList;
if (attestationParameters != null)
{
- var attestationParametersParam = new SqlParameter(null, SqlDbType.VarBinary)
- {
- Direction = ParameterDirection.Input,
- Size = attestationParameters.Length,
- Value = attestationParameters
- };
-
- describeParameterEncryptionRequest.parameters[2] = attestationParametersParam;
+ SqlParameter attestationParametersParam = describeParameterEncryptionRequest.systemParams[2];
+ attestationParametersParam.Direction = ParameterDirection.Input;
+ attestationParametersParam.Size = attestationParameters.Length;
+ attestationParametersParam.Value = attestationParameters;
}
}
@@ -3731,8 +3739,7 @@ private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDi
throw SQL.UnexpectedDescribeParamFormatParameterMetadata();
}
- int paramIdx = 0;
- int parameterStartIndex = 0;
+
// Find the RPC command that generated this tce request
if (BatchRPCMode)
@@ -3756,13 +3763,11 @@ private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDi
Debug.Assert(rpc != null, "rpc should not be null here.");
- // This is the index in the parameters array where the actual parameters start.
- // In BatchRPCMode, parameters[0] has the t-sql, parameters[1] has the param list
- // and actual parameters of the query start at parameters[2].
- parameterStartIndex = (BatchRPCMode ? 2 : 0);
-
+ int userParamCount = rpc.userParams?.Count ?? 0;
+ int recievedMetadataCount = 0;
if (!enclaveMetadataExists || ds.NextResult())
{
+
// Iterate over the parameter names to read the encryption type info
while (ds.Read())
{
@@ -3774,16 +3779,17 @@ private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDi
// When the RPC object gets reused, the parameter array has more parameters that the valid params for the command.
// Null is used to indicate the end of the valid part of the array. Refer to GetRPCObject().
- for (paramIdx = parameterStartIndex; paramIdx < rpc.parameters.Length && rpc.parameters[paramIdx] != null; paramIdx++)
+
+ for (int index = 0; index < userParamCount; index++)
{
- SqlParameter sqlParameter = rpc.parameters[paramIdx];
+ SqlParameter sqlParameter = rpc.userParams[index];
Debug.Assert(sqlParameter != null, "sqlParameter should not be null.");
if (sqlParameter.ParameterNameFixed.Equals(parameterName, StringComparison.Ordinal))
{
Debug.Assert(sqlParameter.CipherMetadata == null, "param.CipherMetadata should be null.");
sqlParameter.HasReceivedMetadata = true;
-
+ recievedMetadataCount += 1;
// Found the param, setup the encryption info.
byte columnEncryptionType = ds.GetByte((int)DescribeParameterEncryptionResultSet2.ColumnEncrytionType);
if ((byte)SqlClientEncryptionType.PlainText != columnEncryptionType)
@@ -3811,7 +3817,9 @@ private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDi
// This is effective only for BatchRPCMode even though we set it for non-BatchRPCMode also,
// since for non-BatchRPCMode mode, paramoptions gets thrown away and reconstructed in BuildExecuteSql.
- rpc.paramoptions[paramIdx] |= TdsEnums.RPC_PARAM_ENCRYPTED;
+ int options = (int)(rpc.userParamMap[index] >> 32);
+ options |= TdsEnums.RPC_PARAM_ENCRYPTED;
+ rpc.userParamMap[index] = ((((long)options) << 32) | (long)index);
}
break;
@@ -3822,15 +3830,20 @@ private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDi
// When the RPC object gets reused, the parameter array has more parameters that the valid params for the command.
// Null is used to indicate the end of the valid part of the array. Refer to GetRPCObject().
- for (paramIdx = parameterStartIndex; paramIdx < rpc.parameters.Length && rpc.parameters[paramIdx] != null; paramIdx++)
+
+ if (recievedMetadataCount != userParamCount)
{
- if (!rpc.parameters[paramIdx].HasReceivedMetadata && rpc.parameters[paramIdx].Direction != ParameterDirection.ReturnValue)
+ for (int index = 0; index < userParamCount; index++)
{
- // Encryption MD wasn't sent by the server - we expect the metadata to be sent for all the parameters
- // that were sent in the original sp_describe_parameter_encryption but not necessarily for return values,
- // since there might be multiple return values but server will only send for one of them.
- // For parameters that don't need encryption, the encryption type is set to plaintext.
- throw SQL.ParamEncryptionMetadataMissing(rpc.parameters[paramIdx].ParameterName, rpc.GetCommandTextOrRpcName());
+ SqlParameter sqlParameter = rpc.userParams[index];
+ if (!sqlParameter.HasReceivedMetadata && sqlParameter.Direction != ParameterDirection.ReturnValue)
+ {
+ // Encryption MD wasn't sent by the server - we expect the metadata to be sent for all the parameters
+ // that were sent in the original sp_describe_parameter_encryption but not necessarily for return values,
+ // since there might be multiple return values but server will only send for one of them.
+ // For parameters that don't need encryption, the encryption type is set to plaintext.
+ throw SQL.ParamEncryptionMetadataMissing(sqlParameter.ParameterName, rpc.GetCommandTextOrRpcName());
+ }
}
}
@@ -4225,7 +4238,7 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi
Debug.Assert(_SqlRPCBatchArray != null, "RunExecuteReader rpc array not provided");
writeTask = _stateObj.Parser.TdsExecuteRPC(this, _SqlRPCBatchArray, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync: !asyncWrite);
}
- else if ((System.Data.CommandType.Text == this.CommandType) && (0 == GetParameterCount(_parameters)))
+ else if ((CommandType.Text == this.CommandType) && (0 == GetParameterCount(_parameters)))
{
// Send over SQL Batch command if we are not a stored proc and have no parameters
Debug.Assert(!IsUserPrepared, "CommandType.Text with no params should not be prepared!");
@@ -4715,86 +4728,54 @@ private void PutStateObject()
}
}
- ///
- /// IMPORTANT NOTE: This is created as a copy of OnDoneProc below for Transparent Column Encryption improvement
- /// as there is not much time, to address regressions. Will revisit removing the duplication, when we have time again.
- ///
internal void OnDoneDescribeParameterEncryptionProc(TdsParserStateObject stateObj)
{
// called per rpc batch complete
if (BatchRPCMode)
{
- // track the records affected for the just completed rpc batch
- // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches
- _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].cumulativeRecordsAffected = _rowsAffected;
-
- _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].recordsAffected =
- (((0 < _currentlyExecutingDescribeParameterEncryptionRPC) && (0 <= _rowsAffected))
- ? (_rowsAffected - Math.Max(_sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC - 1].cumulativeRecordsAffected, 0))
- : _rowsAffected);
-
- // track the error collection (not available from TdsParser after ExecuteNonQuery)
- // and the which errors are associated with the just completed rpc batch
- _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].errorsIndexStart =
- ((0 < _currentlyExecutingDescribeParameterEncryptionRPC)
- ? _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC - 1].errorsIndexEnd
- : 0);
- _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].errorsIndexEnd = stateObj.ErrorCount;
- _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].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
- _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].warningsIndexStart =
- ((0 < _currentlyExecutingDescribeParameterEncryptionRPC)
- ? _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC - 1].warningsIndexEnd
- : 0);
- _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].warningsIndexEnd = stateObj.WarningCount;
- _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].warnings = stateObj._warnings;
-
+ OnDone(stateObj, _currentlyExecutingDescribeParameterEncryptionRPC, _sqlRPCParameterEncryptionReqArray, _rowsAffected);
_currentlyExecutingDescribeParameterEncryptionRPC++;
}
}
- ///
- /// IMPORTANT NOTE: There is a copy of this function above in OnDoneDescribeParameterEncryptionProc.
- /// Please consider the changes being done in this function for the above function as well.
- ///
internal void OnDoneProc()
- { // called per rpc batch complete
+ {
+ // called per rpc batch complete
if (BatchRPCMode)
{
- // track the records affected for the just completed rpc batch
- // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches
- _SqlRPCBatchArray[_currentlyExecutingBatch].cumulativeRecordsAffected = _rowsAffected;
-
- _SqlRPCBatchArray[_currentlyExecutingBatch].recordsAffected =
- (((0 < _currentlyExecutingBatch) && (0 <= _rowsAffected))
- ? (_rowsAffected - Math.Max(_SqlRPCBatchArray[_currentlyExecutingBatch - 1].cumulativeRecordsAffected, 0))
- : _rowsAffected);
-
- // track the error collection (not available from TdsParser after ExecuteNonQuery)
- // and the which errors are associated with the just completed rpc batch
- _SqlRPCBatchArray[_currentlyExecutingBatch].errorsIndexStart =
- ((0 < _currentlyExecutingBatch)
- ? _SqlRPCBatchArray[_currentlyExecutingBatch - 1].errorsIndexEnd
- : 0);
- _SqlRPCBatchArray[_currentlyExecutingBatch].errorsIndexEnd = _stateObj.ErrorCount;
- _SqlRPCBatchArray[_currentlyExecutingBatch].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
- _SqlRPCBatchArray[_currentlyExecutingBatch].warningsIndexStart =
- ((0 < _currentlyExecutingBatch)
- ? _SqlRPCBatchArray[_currentlyExecutingBatch - 1].warningsIndexEnd
- : 0);
- _SqlRPCBatchArray[_currentlyExecutingBatch].warningsIndexEnd = _stateObj.WarningCount;
- _SqlRPCBatchArray[_currentlyExecutingBatch].warnings = _stateObj._warnings;
-
+ OnDone(_stateObj, _currentlyExecutingBatch, _SqlRPCBatchArray, _rowsAffected);
_currentlyExecutingBatch++;
Debug.Assert(_parameterCollectionList.Count >= _currentlyExecutingBatch, "OnDoneProc: Too many DONEPROC events");
}
}
+ private static void OnDone(TdsParserStateObject stateObj, int index, _SqlRPC[] array, int rowsAffected)
+ {
+ _SqlRPC current = array[index];
+ _SqlRPC previous = (index > 0) ? array[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);
+
+ // 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.
@@ -5068,10 +5049,9 @@ private SqlParameter GetParameterForOutputValueExtraction(SqlParameterCollection
return null;
}
- private void GetRPCObject(int paramCount, ref _SqlRPC rpc, bool forSpDescribeParameterEncryption = false)
+ private void GetRPCObject(int systemParamCount, int userParamCount, ref _SqlRPC rpc, bool forSpDescribeParameterEncryption = false)
{
// Designed to minimize necessary allocations
- int ii;
if (rpc == null)
{
if (!forSpDescribeParameterEncryption)
@@ -5098,40 +5078,42 @@ private void GetRPCObject(int paramCount, ref _SqlRPC rpc, bool forSpDescribePar
rpc.ProcID = 0;
rpc.rpcName = null;
rpc.options = 0;
+ rpc.systemParamCount = systemParamCount;
rpc.needsFetchParameterEncryptionMetadata = false;
-
+ int currentCount = rpc.systemParams?.Length ?? 0;
// Make sure there is enough space in the parameters and paramoptions arrays
- if (rpc.parameters == null || rpc.parameters.Length < paramCount)
- {
- rpc.parameters = new SqlParameter[paramCount];
- }
- else if (rpc.parameters.Length > paramCount)
+
+ if (currentCount < systemParamCount)
{
- rpc.parameters[paramCount] = null; // Terminator
+ Array.Resize(ref rpc.systemParams, systemParamCount);
+ Array.Resize(ref rpc.systemParamOptions, systemParamCount);
+ for (int index = currentCount; index < systemParamCount; index++)
+ {
+ rpc.systemParams[index] = new SqlParameter();
+ }
}
- if (rpc.paramoptions == null || (rpc.paramoptions.Length < paramCount))
+
+ for (int ii = 0; ii < systemParamCount; ii++)
{
- rpc.paramoptions = new byte[paramCount];
+ rpc.systemParamOptions[ii] = 0;
}
- else
+
+ if ((rpc.userParamMap?.Length ?? 0) < userParamCount)
{
- for (ii = 0; ii < paramCount; ii++)
- rpc.paramoptions[ii] = 0;
+ Array.Resize(ref rpc.userParamMap, userParamCount);
}
}
- private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlParameterCollection parameters)
+ private void SetUpRPCParameters(_SqlRPC rpc, bool inSchema, SqlParameterCollection parameters)
{
- int ii;
int paramCount = GetParameterCount(parameters);
- int j = startCount;
- TdsParser parser = _activeConnection.Parser;
+ int userParamCount = 0;
- for (ii = 0; ii < paramCount; ii++)
+ for (int index = 0; index < paramCount; index++)
{
- SqlParameter parameter = parameters[ii];
- parameter.Validate(ii, CommandType.StoredProcedure == CommandType);
+ SqlParameter parameter = parameters[index];
+ parameter.Validate(index, CommandType.StoredProcedure == CommandType);
// func will change type to that with a 4 byte length if the type has a two
// byte length and a parameter length > than that expressible in 2 bytes
@@ -5142,20 +5124,20 @@ private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlP
if (ShouldSendParameter(parameter))
{
- rpc.parameters[j] = parameter;
+ byte options = 0;
// set output bit
- if (parameter.Direction == ParameterDirection.InputOutput ||
- parameter.Direction == ParameterDirection.Output)
- rpc.paramoptions[j] = TdsEnums.RPC_PARAM_BYREF;
+ if (parameter.Direction == ParameterDirection.InputOutput || parameter.Direction == ParameterDirection.Output)
+ {
+ options = TdsEnums.RPC_PARAM_BYREF;
+ }
// Set the encryped bit, if the parameter is to be encrypted.
if (parameter.CipherMetadata != null)
{
- rpc.paramoptions[j] |= TdsEnums.RPC_PARAM_ENCRYPTED;
+ options |= TdsEnums.RPC_PARAM_ENCRYPTED;
}
-
// set default value bit
if (parameter.Direction != ParameterDirection.Output)
{
@@ -5166,54 +5148,60 @@ private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlP
// TVPs use DEFAULT and do not allow NULL, even for schema only.
if (null == parameter.Value && (!inSchema || SqlDbType.Structured == parameter.SqlDbType))
{
- rpc.paramoptions[j] |= TdsEnums.RPC_PARAM_DEFAULT;
+ options |= TdsEnums.RPC_PARAM_DEFAULT;
}
}
+ rpc.userParamMap[userParamCount] = ((((long)options) << 32) | (long)index);
+ userParamCount += 1;
// Must set parameter option bit for LOB_COOKIE if unfilled LazyMat blob
- j++;
}
}
+
+ rpc.userParamCount = userParamCount;
+ rpc.userParams = parameters;
}
private _SqlRPC BuildPrepExec(CommandBehavior behavior)
{
Debug.Assert(System.Data.CommandType.Text == this.CommandType, "invalid use of sp_prepexec for stored proc invocation!");
SqlParameter sqlParam;
- int j = 3;
- int count = CountSendableParameters(_parameters);
+ const int systemParameterCount = 3;
+ int userParameterCount = CountSendableParameters(_parameters);
_SqlRPC rpc = null;
- GetRPCObject(count + j, ref rpc);
+ GetRPCObject(systemParameterCount, userParameterCount, ref rpc);
rpc.ProcID = TdsEnums.RPC_PROCID_PREPEXEC;
rpc.rpcName = TdsEnums.SP_PREPEXEC;
//@handle
- sqlParam = new SqlParameter(null, SqlDbType.Int);
- sqlParam.Direction = ParameterDirection.InputOutput;
+ sqlParam = rpc.systemParams[0];
+ sqlParam.SqlDbType = SqlDbType.Int;
sqlParam.Value = _prepareHandle;
- rpc.parameters[0] = sqlParam;
- rpc.paramoptions[0] = TdsEnums.RPC_PARAM_BYREF;
+ sqlParam.Size = 4;
+ sqlParam.Direction = ParameterDirection.InputOutput;
+ rpc.systemParamOptions[0] = TdsEnums.RPC_PARAM_BYREF;
//@batch_params
string paramList = BuildParamList(_stateObj.Parser, _parameters);
- sqlParam = new SqlParameter(null, ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, paramList.Length);
+ sqlParam = rpc.systemParams[1];
+ sqlParam.SqlDbType = ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
sqlParam.Value = paramList;
- rpc.parameters[1] = sqlParam;
+ sqlParam.Size = paramList.Length;
//@batch_text
string text = GetCommandText(behavior);
- sqlParam = new SqlParameter(null, ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, text.Length);
+ sqlParam = rpc.systemParams[2];
+ sqlParam.SqlDbType = ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
+ sqlParam.Size = text.Length;
sqlParam.Value = text;
- rpc.parameters[2] = sqlParam;
- SetUpRPCParameters(rpc, j, false, _parameters);
+ SetUpRPCParameters(rpc, false, _parameters);
return rpc;
}
-
//
// returns true if the parameter is not a return value
// and it's value is not DBNull (for a nullable parameter)
@@ -5246,7 +5234,9 @@ private int CountSendableParameters(SqlParameterCollection parameters)
for (int i = 0; i < count; i++)
{
if (ShouldSendParameter(parameters[i]))
+ {
cParams++;
+ }
}
}
return cParams;
@@ -5255,7 +5245,7 @@ private int CountSendableParameters(SqlParameterCollection parameters)
// Returns total number of parameters
private int GetParameterCount(SqlParameterCollection parameters)
{
- return ((null != parameters) ? parameters.Count : 0);
+ return (null != parameters) ? parameters.Count : 0;
}
//
@@ -5264,20 +5254,15 @@ private int GetParameterCount(SqlParameterCollection parameters)
private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _SqlRPC rpc)
{
Debug.Assert(this.CommandType == System.Data.CommandType.StoredProcedure, "Command must be a stored proc to execute an RPC");
- int count = CountSendableParameters(parameters);
- GetRPCObject(count, ref rpc);
+ int userParameterCount = CountSendableParameters(parameters);
+ GetRPCObject(0, userParameterCount, ref rpc);
+ rpc.ProcID = 0;
rpc.rpcName = this.CommandText; // just get the raw command text
- SetUpRPCParameters(rpc, 0, inSchema, parameters);
+ SetUpRPCParameters(rpc, inSchema, parameters);
}
- //
- // build the RPC record header for sp_unprepare
- //
- // prototype for sp_unprepare is:
- // sp_unprepare(@handle)
-
// build the RPC record header for sp_execute
//
// prototype for sp_execute is:
@@ -5286,24 +5271,23 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql
private _SqlRPC BuildExecute(bool inSchema)
{
Debug.Assert((int)_prepareHandle != -1, "Invalid call to sp_execute without a valid handle!");
- int j = 1;
+ const int systemParameterCount = 1;
- int count = CountSendableParameters(_parameters);
+ int userParameterCount = CountSendableParameters(_parameters);
_SqlRPC rpc = null;
- GetRPCObject(count + j, ref rpc);
-
- SqlParameter sqlParam;
+ GetRPCObject(systemParameterCount, userParameterCount, ref rpc);
rpc.ProcID = TdsEnums.RPC_PROCID_EXECUTE;
rpc.rpcName = TdsEnums.SP_EXECUTE;
//@handle
- sqlParam = new SqlParameter(null, SqlDbType.Int);
+ SqlParameter sqlParam = rpc.systemParams[0];
+ sqlParam.SqlDbType = SqlDbType.Int;
sqlParam.Value = _prepareHandle;
- rpc.parameters[0] = sqlParam;
+ sqlParam.Direction = ParameterDirection.Input;
- SetUpRPCParameters(rpc, j, inSchema, _parameters);
+ SetUpRPCParameters(rpc, inSchema, _parameters);
return rpc;
}
@@ -5317,20 +5301,20 @@ private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlPa
Debug.Assert((int)_prepareHandle == -1, "This command has an existing handle, use sp_execute!");
Debug.Assert(CommandType.Text == this.CommandType, "invalid use of sp_executesql for stored proc invocation!");
- int j;
+ int systemParamCount;
SqlParameter sqlParam;
- int cParams = CountSendableParameters(parameters);
- if (cParams > 0)
+ int userParamCount = CountSendableParameters(parameters);
+ if (userParamCount > 0)
{
- j = 2;
+ systemParamCount = 2;
}
else
{
- j = 1;
+ systemParamCount = 1;
}
- GetRPCObject(cParams + j, ref rpc);
+ GetRPCObject(systemParamCount, userParamCount, ref rpc);
rpc.ProcID = TdsEnums.RPC_PROCID_EXECUTESQL;
rpc.rpcName = TdsEnums.SP_EXECUTESQL;
@@ -5339,19 +5323,22 @@ private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlPa
{
commandText = GetCommandText(behavior);
}
- sqlParam = new SqlParameter(null, ((commandText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, commandText.Length);
+ sqlParam = rpc.systemParams[0];
+ sqlParam.SqlDbType = ((commandText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
+ sqlParam.Size = commandText.Length;
sqlParam.Value = commandText;
- rpc.parameters[0] = sqlParam;
+ sqlParam.Direction = ParameterDirection.Input;
- if (cParams > 0)
+ if (userParamCount > 0)
{
string paramList = BuildParamList(_stateObj.Parser, BatchRPCMode ? parameters : _parameters);
- sqlParam = new SqlParameter(null, ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, paramList.Length);
+ sqlParam = rpc.systemParams[1];
+ sqlParam.SqlDbType = ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
+ sqlParam.Size = paramList.Length;
sqlParam.Value = paramList;
- rpc.parameters[1] = sqlParam;
bool inSchema = (0 != (behavior & CommandBehavior.SchemaOnly));
- SetUpRPCParameters(rpc, j, inSchema, parameters);
+ SetUpRPCParameters(rpc, inSchema, parameters);
}
}
@@ -5363,7 +5350,7 @@ private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlPa
/// Stored procedure name
/// SqlParameter list
/// A string SqlParameter containing the constructed sql statement value
- private SqlParameter BuildStoredProcedureStatementForColumnEncryption(string storedProcedureName, SqlParameter[] parameters)
+ private SqlParameter BuildStoredProcedureStatementForColumnEncryption(string storedProcedureName, SqlParameterCollection parameters)
{
Debug.Assert(CommandType == CommandType.StoredProcedure, "BuildStoredProcedureStatementForColumnEncryption() should only be called for stored procedures");
Debug.Assert(!string.IsNullOrWhiteSpace(storedProcedureName), "storedProcedureName cannot be null or empty in BuildStoredProcedureStatementForColumnEncryption");
@@ -5396,27 +5383,27 @@ private SqlParameter BuildStoredProcedureStatementForColumnEncryption(string sto
// @param1=@param1, @param1=@param2, ..., @paramn=@paramn
// Append the first parameter
- int i = 0;
-
- if (parameters.Count() > 0)
+ int index = 0;
+ int count = parameters.Count;
+ if (count > 0)
{
// Skip the return value parameters.
- while (i < parameters.Count() && parameters[i].Direction == ParameterDirection.ReturnValue)
+ while (index < parameters.Count && parameters[index].Direction == ParameterDirection.ReturnValue)
{
- i++;
+ index++;
}
- if (i < parameters.Count())
+ if (index < count)
{
// Possibility of a SQL Injection issue through parameter names and how to construct valid identifier for parameters.
// Since the parameters comes from application itself, there should not be a security vulnerability.
// Also since the query is not executed, but only analyzed there is no possibility for elevation of priviledge, but only for
// incorrect results which would only affect the user that attempts the injection.
- execStatement.AppendFormat(@" {0}={0}", parameters[i].ParameterNameFixed);
+ execStatement.AppendFormat(@" {0}={0}", parameters[index].ParameterNameFixed);
// InputOutput and Output parameters need to be marked as such.
- if (parameters[i].Direction == ParameterDirection.Output ||
- parameters[i].Direction == ParameterDirection.InputOutput)
+ if (parameters[index].Direction == ParameterDirection.Output ||
+ parameters[index].Direction == ParameterDirection.InputOutput)
{
execStatement.AppendFormat(@" OUTPUT");
}
@@ -5424,18 +5411,20 @@ private SqlParameter BuildStoredProcedureStatementForColumnEncryption(string sto
}
// Move to the next parameter.
- i++;
+ index++;
// Append the rest of parameters
- for (; i < parameters.Count(); i++)
+ for (; index < count; index++)
{
- if (parameters[i].Direction != ParameterDirection.ReturnValue)
+ if (parameters[index].Direction != ParameterDirection.ReturnValue)
{
- execStatement.AppendFormat(@", {0}={0}", parameters[i].ParameterNameFixed);
+ execStatement.AppendFormat(@", {0}={0}", parameters[index].ParameterNameFixed);
// InputOutput and Output parameters need to be marked as such.
- if (parameters[i].Direction == ParameterDirection.Output ||
- parameters[i].Direction == ParameterDirection.InputOutput)
+ if (
+ parameters[index].Direction == ParameterDirection.Output ||
+ parameters[index].Direction == ParameterDirection.InputOutput
+ )
{
execStatement.AppendFormat(@" OUTPUT");
}
@@ -5597,7 +5586,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete
// Adds quotes to each part of a SQL identifier that may be multi-part, while leaving
// the result as a single composite name.
- private string ParseAndQuoteIdentifier(string identifier, bool isUdtTypeName)
+ private static string ParseAndQuoteIdentifier(string identifier, bool isUdtTypeName)
{
string[] strings = SqlParameter.ParseTypeName(identifier, isUdtTypeName);
return ADP.BuildMultiPartName(strings);
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
index c331e19a95..ccb91ccef5 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -8704,12 +8704,12 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
}
// Stream out parameters
- SqlParameter[] parameters = rpcext.parameters;
+ int parametersLength = rpcext.userParamCount + rpcext.systemParamCount;
- for (int i = (ii == startRpc) ? startParam : 0; i < parameters.Length; i++)
+ for (int i = (ii == startRpc) ? startParam : 0; i < parametersLength; i++)
{
- // parameters can be unnamed
- SqlParameter param = parameters[i];
+ byte options = 0;
+ SqlParameter param = rpcext.GetParameterByIndex(i, out options);
// Since we are reusing the parameters array, we cannot rely on length to indicate no of parameters.
if (param == null)
break; // End of parameters for this execute
@@ -8738,7 +8738,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
if (mt.IsNewKatmaiType)
{
- WriteSmiParameter(param, i, 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_DEFAULT), stateObj);
+ WriteSmiParameter(param, i, 0 != (options & TdsEnums.RPC_PARAM_DEFAULT), stateObj);
continue;
}
@@ -8747,226 +8747,353 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
{
throw ADP.VersionDoesNotSupportDataType(mt.TypeName);
}
- object value = null;
- bool isNull = true;
- bool isSqlVal = false;
- bool isDataFeed = false;
- // if we have an output param, set the value to null so we do not send it across to the server
- if (param.Direction == ParameterDirection.Output)
- {
- isSqlVal = param.ParameterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the parameter we will no longer be able to distinguish what type were seeing.
- param.Value = null;
- param.ParameterIsSqlType = isSqlVal;
- }
- else
- {
- value = param.GetCoercedValue();
- isNull = param.IsNull;
- if (!isNull)
- {
- isSqlVal = param.CoercedValueIsSqlType;
- isDataFeed = param.CoercedValueIsDataFeed;
- }
- }
- WriteParameterName(param.ParameterNameFixed, stateObj);
+ Task writeParamTask = TDSExecuteRPCAddParameter(stateObj, param, mt, options);
- // Write parameter status
- stateObj.WriteByte(rpcext.paramoptions[i]);
-
- // MaxLen field is only written out for non-fixed length data types
- // use the greater of the two sizes for maxLen
- int actualSize;
- int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize();
-
- //for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation
- if (mt.TDSType != TdsEnums.SQLUDT)
- // getting the actualSize is expensive, cache here and use below
- actualSize = param.GetActualSize();
- else
- actualSize = 0; //get this later
-
- byte precision = 0;
- byte scale = 0;
-
- // scale and precision are only relevant for numeric and decimal types
- // adjust the actual value scale and precision to match the user specified
- if (mt.SqlDbType == SqlDbType.Decimal)
+ if (!sync)
{
- precision = param.GetActualPrecision();
- scale = param.GetActualScale();
-
- if (precision > TdsEnums.MAX_NUMERIC_PRECISION)
+ if (writeParamTask == null)
{
- throw SQL.PrecisionValueOutOfRange(precision);
+ writeParamTask = stateObj.WaitForAccumulatedWrites();
}
- // bug 49512, make sure the value matches the scale the user enters
- if (!isNull)
+ if (writeParamTask != null)
{
- if (isSqlVal)
+ Task task = null;
+ if (completion == null)
{
- value = AdjustSqlDecimalScale((SqlDecimal)value, scale);
-
- // If Precision is specified, verify value precision vs param precision
- if (precision != 0)
- {
- if (precision < ((SqlDecimal)value).Precision)
- {
- throw ADP.ParameterValueOutOfRange((SqlDecimal)value);
- }
- }
+ completion = new TaskCompletionSource