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 bca9f13ea7..b576ef3510 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
@@ -4058,6 +4058,7 @@ private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcReques
tsqlParam.SqlDbType = ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
tsqlParam.Value = text;
tsqlParam.Size = text.Length;
+ tsqlParam.Direction = ParameterDirection.Input;
}
else
{
@@ -4075,6 +4076,7 @@ private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcReques
tsqlParam.SqlDbType = ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
tsqlParam.Value = text;
tsqlParam.Size = text.Length;
+ tsqlParam.Direction = ParameterDirection.Input;
}
}
@@ -4151,13 +4153,15 @@ private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcReques
paramsParam.SqlDbType = ((parameterList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
paramsParam.Size = parameterList.Length;
paramsParam.Value = parameterList;
+ paramsParam.Direction = ParameterDirection.Input;
if (attestationParameters != null)
{
SqlParameter attestationParametersParam = describeParameterEncryptionRequest.systemParams[2];
- attestationParametersParam.Direction = ParameterDirection.Input;
+ attestationParametersParam.SqlDbType = SqlDbType.VarBinary;
attestationParametersParam.Size = attestationParameters.Length;
attestationParametersParam.Value = attestationParameters;
+ attestationParametersParam.Direction = ParameterDirection.Input;
}
}
@@ -5791,6 +5795,7 @@ private _SqlRPC BuildPrepExec(CommandBehavior behavior)
sqlParam.SqlDbType = ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
sqlParam.Value = paramList;
sqlParam.Size = paramList.Length;
+ sqlParam.Direction = ParameterDirection.Input;
//@batch_text
string text = GetCommandText(behavior);
@@ -5798,6 +5803,7 @@ private _SqlRPC BuildPrepExec(CommandBehavior behavior)
sqlParam.SqlDbType = ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
sqlParam.Size = text.Length;
sqlParam.Value = text;
+ sqlParam.Direction = ParameterDirection.Input;
SetUpRPCParameters(rpc, false, _parameters);
return rpc;
@@ -5899,6 +5905,7 @@ private _SqlRPC BuildExecute(bool inSchema)
//@handle
SqlParameter sqlParam = rpc.systemParams[0];
sqlParam.SqlDbType = SqlDbType.Int;
+ sqlParam.Size = 4;
sqlParam.Value = _prepareHandle;
sqlParam.Direction = ParameterDirection.Input;
@@ -5951,6 +5958,7 @@ private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlPa
sqlParam.SqlDbType = ((paramList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText;
sqlParam.Size = paramList.Length;
sqlParam.Value = paramList;
+ sqlParam.Direction = ParameterDirection.Input;
bool inSchema = (0 != (behavior & CommandBehavior.SchemaOnly));
SetUpRPCParameters(rpc, inSchema, parameters);
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs
index 7294c745d1..fdfa0772ea 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs
@@ -4449,7 +4449,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;
@@ -4494,20 +4494,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], serializedAttestationParameters);
@@ -4573,20 +4564,24 @@ 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;
+ tsqlParam.Direction = ParameterDirection.Input;
}
else
{
@@ -4595,42 +4590,58 @@ 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;
+ tsqlParam.Direction = ParameterDirection.Input;
}
}
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)
+ 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;
@@ -4659,20 +4670,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;
+ 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;
+ paramsParam.Direction = ParameterDirection.Input;
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.SqlDbType = SqlDbType.VarBinary;
+ attestationParametersParam.Size = attestationParameters.Length;
+ attestationParametersParam.Value = attestationParameters;
+ attestationParametersParam.Direction = ParameterDirection.Input;
}
}
@@ -4824,9 +4834,6 @@ 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)
{
@@ -4849,10 +4856,8 @@ 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())
{
@@ -4867,16 +4872,16 @@ 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.ColumnEncryptionType);
if ((byte)SqlClientEncryptionType.PlainText != columnEncryptionType)
@@ -4904,7 +4909,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;
@@ -4915,15 +4922,19 @@ 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());
+ }
}
}
@@ -6093,40 +6104,41 @@ internal void OnDoneDescribeParameterEncryptionProc(TdsParserStateObject stateOb
/// 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)
{
+ OnDone(_stateObj, _currentlyExecutingBatch, _SqlRPCBatchArray, _rowsAffected);
+ _currentlyExecutingBatch++;
+ Debug.Assert(_parameterCollectionList.Count >= _currentlyExecutingBatch, "OnDoneProc: Too many DONEPROC events");
+ }
+ }
- // track the records affected for the just completed rpc batch
- // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches
- _SqlRPCBatchArray[_currentlyExecutingBatch].cumulativeRecordsAffected = _rowsAffected;
+ private static void OnDone(TdsParserStateObject stateObj, int index, _SqlRPC[] array, int rowsAffected)
+ {
+ _SqlRPC current = array[index];
+ _SqlRPC previous = (index > 0) ? array[index - 1] : null;
- _SqlRPCBatchArray[_currentlyExecutingBatch].recordsAffected =
- (((0 < _currentlyExecutingBatch) && (0 <= _rowsAffected))
- ? (_rowsAffected - Math.Max(_SqlRPCBatchArray[_currentlyExecutingBatch - 1].cumulativeRecordsAffected, 0))
- : _rowsAffected);
+ // track the records affected for the just completed rpc batch
+ // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches
+ current.cumulativeRecordsAffected = 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;
+ current.recordsAffected =
+ (((previous != null) && (0 <= rowsAffected))
+ ? (rowsAffected - Math.Max(previous.cumulativeRecordsAffected, 0))
+ : rowsAffected);
- // 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;
+ // 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;
- _currentlyExecutingBatch++;
- Debug.Assert(_parameterCollectionList.Count >= _currentlyExecutingBatch, "OnDoneProc: Too many DONEPROC events");
- }
+ // 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;
}
//
@@ -6459,10 +6471,10 @@ 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)
@@ -6489,6 +6501,7 @@ private void GetRPCObject(int paramCount, ref _SqlRPC rpc, bool forSpDescribePar
rpc.ProcID = 0;
rpc.rpcName = null;
rpc.options = 0;
+ rpc.systemParamCount = systemParamCount;
rpc.recordsAffected = default(int?);
rpc.cumulativeRecordsAffected = -1;
@@ -6502,37 +6515,39 @@ private void GetRPCObject(int paramCount, ref _SqlRPC rpc, bool forSpDescribePar
rpc.warnings = null;
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 expressable in 2 bytes
@@ -6543,17 +6558,18 @@ 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
@@ -6566,7 +6582,7 @@ private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlP
// SQLBUVSTS 179488 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;
}
// detect incorrectly derived type names unchanged by the caller and fix them
@@ -6587,11 +6603,15 @@ private void SetUpRPCParameters(_SqlRPC rpc, int startCount, bool inSchema, SqlP
}
}
+ 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;
}
//
@@ -6603,36 +6623,41 @@ 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;
+ sqlParam.Direction = ParameterDirection.Input;
//@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;
+ sqlParam.Direction = ParameterDirection.Input;
- SetUpRPCParameters(rpc, j, false, _parameters);
+ SetUpRPCParameters(rpc, false, _parameters);
return rpc;
}
@@ -6689,14 +6714,14 @@ 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);
// TDS Protocol allows rpc name with maximum length of 1046 bytes for ProcName
// 4-part name 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 = 523
// each char takes 2 bytes. 523 * 2 = 1046
int commandTextLength = ADP.CharSize * CommandText.Length;
-
+ rpc.ProcID = 0;
if (commandTextLength <= MaxRPCNameLength)
{
rpc.rpcName = CommandText; // just get the raw command text
@@ -6706,33 +6731,7 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql
throw ADP.InvalidArgumentLength(nameof(CommandText), MaxRPCNameLength);
}
- SetUpRPCParameters(rpc, 0, inSchema, parameters);
- }
-
- //
- // build the RPC record header for sp_unprepare
- //
- // prototype for sp_unprepare is:
- // sp_unprepare(@handle)
- //
- // CONSIDER: instead of creating each time, define at load time and then put the new value in
- private _SqlRPC BuildUnprepare()
- {
- Debug.Assert(_prepareHandle != 0, "Invalid call to sp_unprepare without a valid handle!");
-
- _SqlRPC rpc = null;
- GetRPCObject(1, ref rpc);
- SqlParameter sqlParam;
-
- rpc.ProcID = TdsEnums.RPC_PROCID_UNPREPARE;
- rpc.rpcName = TdsEnums.SP_UNPREPARE;
-
- //@handle
- sqlParam = new SqlParameter(null, SqlDbType.Int);
- sqlParam.Value = _prepareHandle;
- rpc.parameters[0] = sqlParam;
-
- return rpc;
+ SetUpRPCParameters(rpc, inSchema, parameters);
}
//
@@ -6744,24 +6743,24 @@ private _SqlRPC BuildUnprepare()
private _SqlRPC BuildExecute(bool inSchema)
{
Debug.Assert(_prepareHandle != -1, "Invalid call to sp_execute without a valid handle!");
- int j = 1;
- int count = CountSendableParameters(_parameters);
+ const int systemParameterCount = 1;
+ 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.Size = 4;
+ sqlParam.Direction = ParameterDirection.Input;
- SetUpRPCParameters(rpc, j, inSchema, _parameters);
+ SetUpRPCParameters(rpc, inSchema, _parameters);
return rpc;
}
@@ -6775,20 +6774,20 @@ private void BuildExecuteSql(CommandBehavior behavior, string commandText, SqlPa
Debug.Assert(_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;
@@ -6797,19 +6796,23 @@ 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;
+ sqlParam.Direction = ParameterDirection.Input;
bool inSchema = (0 != (behavior & CommandBehavior.SchemaOnly));
- SetUpRPCParameters(rpc, j, inSchema, parameters);
+ SetUpRPCParameters(rpc, inSchema, parameters);
}
}
@@ -6821,7 +6824,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");
@@ -6865,27 +6868,29 @@ 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");
}
@@ -6893,18 +6898,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");
}
@@ -6924,9 +6931,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete
StringBuilder paramList = new StringBuilder();
bool fAddSeparator = false;
- int count = 0;
-
- count = parameters.Count;
+ int count = parameters.Count;
for (int i = 0; i < count; i++)
{
SqlParameter sqlParam = parameters[i];
@@ -7073,7 +7078,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);
@@ -7159,48 +7164,6 @@ private string GetCommandText(CommandBehavior behavior)
return GetSetOptionsString(behavior) + this.CommandText;
}
- //
- // build the RPC record header for sp_executesql and add the parameters
- //
- // the prototype for sp_prepare is:
- // sp_prepare(@handle int OUTPUT, @batch_params ntext, @batch_text ntext, @options int default 0x1)
- private _SqlRPC BuildPrepare(CommandBehavior behavior)
- {
- Debug.Assert(System.Data.CommandType.Text == this.CommandType, "invalid use of sp_prepare for stored proc invocation!");
-
- _SqlRPC rpc = null;
- GetRPCObject(3, ref rpc);
- SqlParameter sqlParam;
-
- rpc.ProcID = TdsEnums.RPC_PROCID_PREPARE;
- rpc.rpcName = TdsEnums.SP_PREPARE;
-
- //@handle
- sqlParam = new SqlParameter(null, SqlDbType.Int);
- sqlParam.Direction = ParameterDirection.Output;
- rpc.parameters[0] = sqlParam;
- rpc.paramoptions[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.Value = paramList;
- rpc.parameters[1] = sqlParam;
-
- //@batch_text
- string text = GetCommandText(behavior);
- sqlParam = new SqlParameter(null, ((text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, text.Length);
- sqlParam.Value = text;
- rpc.parameters[2] = sqlParam;
-
- /*
- //@options
- sqlParam = new SqlParameter(null, SqlDbType.Int);
- rpc.Parameters[3] = sqlParam;
- */
- return rpc;
- }
-
internal void CheckThrowSNIException()
{
var stateObj = _stateObj;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
index 1edad799ae..c35ae13e63 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -9963,16 +9963,17 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
WriteEnclaveInfo(stateObj, enclavePackage);
// Stream out parameters
- SqlParameter[] parameters = rpcext.parameters;
+ int parametersLength = rpcext.userParamCount + rpcext.systemParamCount;
bool isAdvancedTraceOn = SqlClientEventSource.Log.IsAdvancedTraceOn();
bool enableOptimizedParameterBinding = cmd.EnableOptimizedParameterBinding;
- for (int i = (ii == startRpc) ? startParam : 0; i < parameters.Length; i++)
+ for (int i = (ii == startRpc) ? startParam : 0; i < parametersLength; i++)
{
// Debug.WriteLine("i: " + i.ToString(CultureInfo.InvariantCulture));
// 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)
{
@@ -10010,7 +10011,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
if (mt.Is2008Type)
{
- WriteSmiParameter(param, i, 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_DEFAULT), stateObj, enableOptimizedParameterBinding, isAdvancedTraceOn);
+ WriteSmiParameter(param, i, 0 != (options & TdsEnums.RPC_PARAM_DEFAULT), stateObj, enableOptimizedParameterBinding, isAdvancedTraceOn);
continue;
}
@@ -10045,7 +10046,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
WriteParameterName(param.ParameterNameFixed, stateObj, enableOptimizedParameterBinding);
// Write parameter status
- stateObj.WriteByte(rpcext.paramoptions[i]);
+ stateObj.WriteByte(options);
// MaxLen field is only written out for non-fixed length data types
// use the greater of the two sizes for maxLen
@@ -10108,7 +10109,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
}
}
- bool isParameterEncrypted = 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_ENCRYPTED);
+ bool isParameterEncrypted = 0 != (options & TdsEnums.RPC_PARAM_ENCRYPTED);
// Additional information we need to send over wire to the server when writing encrypted parameters.
SqlColumnEncryptionInputParameterInfo encryptedParameterInfoToWrite = null;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
index bf113efe3b..62f7f59b91 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
@@ -1158,11 +1158,16 @@ private void SerializeIntIntoBuffer(int value, byte[] buffer, ref int offset)
sealed internal class _SqlRPC
{
internal string rpcName;
- internal string databaseName; // Used for UDTs
internal ushort ProcID; // Used instead of name
internal ushort options;
- internal SqlParameter[] parameters;
- internal byte[] paramoptions;
+
+ internal SqlParameter[] systemParams;
+ internal byte[] systemParamOptions;
+ internal int systemParamCount;
+
+ internal SqlParameterCollection userParams;
+ internal long[] userParamMap;
+ internal int userParamCount;
internal int? recordsAffected;
internal int cumulativeRecordsAffected;
@@ -1175,18 +1180,37 @@ sealed internal class _SqlRPC
internal int warningsIndexEnd;
internal SqlErrorCollection warnings;
internal bool needsFetchParameterEncryptionMetadata;
+
internal string GetCommandTextOrRpcName()
{
if (TdsEnums.RPC_PROCID_EXECUTESQL == ProcID)
{
// Param 0 is the actual sql executing
- return (string)parameters[0].Value;
+ return (string)systemParams[0].Value;
}
else
{
return rpcName;
}
}
+
+ internal SqlParameter GetParameterByIndex(int index, out byte options)
+ {
+ SqlParameter retval;
+ if (index < systemParamCount)
+ {
+ retval = systemParams[index];
+ options = systemParamOptions[index];
+ }
+ else
+ {
+ long data = userParamMap[index - systemParamCount];
+ int paramIndex = (int)(data & int.MaxValue);
+ options = (byte)((data >> 32) & 0xFF);
+ retval = userParams[paramIndex];
+ }
+ return retval;
+ }
}
sealed internal class SqlReturnValue : SqlMetaDataPriv