From a351b5d00d94ea26533feac38646774231fb5ffc Mon Sep 17 00:00:00 2001 From: Wraith Date: Thu, 8 Jul 2021 00:55:15 +0100 Subject: [PATCH] Add SqlCommand.DisableOutputParameters Feature (#1041) --- .../Microsoft.Data.SqlClient/SqlCommand.xml | 95 ++++++-- .../netcore/ref/Microsoft.Data.SqlClient.cs | 2 + .../Microsoft/Data/SqlClient/SqlCommand.cs | 3 + .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 5 + .../src/Microsoft/Data/SqlClient/TdsParser.cs | 32 ++- .../netcore/src/Resources/Strings.Designer.cs | 9 + .../netcore/src/Resources/Strings.resx | 3 + .../netfx/ref/Microsoft.Data.SqlClient.cs | 2 + .../Microsoft/Data/SqlClient/SqlCommand.cs | 3 + .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 5 + .../src/Microsoft/Data/SqlClient/TdsParser.cs | 30 ++- .../netfx/src/Resources/Strings.Designer.cs | 60 +++-- .../netfx/src/Resources/Strings.resx | 5 +- .../SQL/ParameterTest/ParametersTest.cs | 225 ++++++++++++++++++ 14 files changed, 419 insertions(+), 60 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml index 68b10ce395..62ae7811a8 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml @@ -286,8 +286,13 @@ The following console application creates updates data within the **AdventureWor The - closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). - + closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). + + - or - + + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. + An error occurred in a @@ -399,8 +404,13 @@ To set up this example, create a new Windows application. Put a The - closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). - + closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). + + - or - + + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. + @@ -477,8 +487,13 @@ The following console application starts the process of retrieving a data reader The - closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). - + closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). + + - or - + + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. + An error occurred in a @@ -584,8 +599,13 @@ This example also passes the `CommandBehavior.CloseConnection` and `CommandBehav The - closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). - + closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). + + - or - + + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. + An error occurred in a @@ -702,8 +722,13 @@ To set up this example, create a new Windows application. Put a The - closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). - + closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). + + - or - + + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. + An error occurred in a @@ -831,8 +856,13 @@ This example passes the `CommandBehavior.CloseConnection` value in the `behavior The - closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). - + closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). + + - or - + + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. + An error occurred in a @@ -940,6 +970,11 @@ The following console application starts the process of retrieving XML data asyn The closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). + + - or - + + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. An error occurred in a @@ -1067,8 +1102,13 @@ To set up this example, create a new Windows application. Put a The - closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). - + closed or dropped during a streaming operation. For more information about streaming, see [SqlClient Streaming Support](/sql/connect/ado-net/sqlclient-streaming-support). + + - or - + + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. + An error occurred in a @@ -1343,6 +1383,33 @@ The method is a st To be added. + + + Gets or sets a value indicating whether the command object should optimize parameter performance by disabling Output and InputOutput directions when submitting the command to the SQL Server. + + + A value indicating whether the command object should optimize parameter performance by disabling Output and InputOuput parameter directions when submitting the command to the SQL Server. + The default is . + + + + [!NOTE] +If the option is enabled and a parameter with Direction Output or InputOutput is present in the Parameters collection an InvalidOperationException will be thrown when the command is executed. + +]]> + + + The diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index b8c4c8a15e..1fa1f14d05 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -534,6 +534,8 @@ public SqlCommand(string cmdText, Microsoft.Data.SqlClient.SqlConnection connect [System.ComponentModel.DesignOnlyAttribute(true)] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool DesignTimeVisible { get { throw null; } set { } } + /// + public bool EnableOptimizedParameterBinding { get { throw null; } set { } } /// public new Microsoft.Data.SqlClient.SqlParameterCollection Parameters { get { throw null; } } /// 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 958dbe893f..57d8fdf4b7 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 @@ -692,6 +692,9 @@ public override bool DesignTimeVisible } } + /// + public bool EnableOptimizedParameterBinding { get; set; } + /// new public SqlParameterCollection Parameters { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs index 46c874ab80..745e820948 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -495,6 +495,11 @@ internal static Exception ParameterCannotBeEmpty(string paramName) return ADP.ArgumentNull(System.StringsHelper.GetString(Strings.SQL_ParameterCannotBeEmpty, paramName)); } + internal static Exception ParameterDirectionInvalidForOptimizedBinding(string paramName) + { + return ADP.InvalidOperation(System.StringsHelper.GetString(Strings.SQL_ParameterDirectionInvalidForOptimizedBinding, paramName)); + } + internal static Exception ActiveDirectoryInteractiveTimeout() { return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_Interactive_Authentication); 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 204b736715..cb55efbee1 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 @@ -8988,6 +8988,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo int parametersLength = rpcext.userParamCount + rpcext.systemParamCount; bool isAdvancedTraceOn = SqlClientEventSource.Log.IsAdvancedTraceOn(); + bool enableOptimizedParameterBinding = cmd.EnableOptimizedParameterBinding; for (int i = (ii == startRpc) ? startParam : 0; i < parametersLength; i++) { @@ -8995,7 +8996,11 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo 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 + } + + ParameterDirection parameterDirection = param.Direction; // Throw an exception if ForceColumnEncryption is set on a parameter and the ColumnEncryption is not enabled on SqlConnection or SqlCommand if (param.ForceColumnEncryption && @@ -9007,12 +9012,17 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo // Check if the applications wants to force column encryption to avoid sending sensitive data to server if (param.ForceColumnEncryption && param.CipherMetadata == null - && (param.Direction == ParameterDirection.Input || param.Direction == ParameterDirection.InputOutput)) + && (parameterDirection == ParameterDirection.Input || parameterDirection == ParameterDirection.InputOutput)) { // Application wants a parameter to be encrypted before sending it to server, however server doesnt think this parameter needs encryption. throw SQL.ParamUnExpectedEncryptionMetadata(param.ParameterName, rpcext.GetCommandTextOrRpcName()); } + if (enableOptimizedParameterBinding && (parameterDirection == ParameterDirection.Output || parameterDirection == ParameterDirection.InputOutput)) + { + throw SQL.ParameterDirectionInvalidForOptimizedBinding(param.ParameterName); + } + // Validate parameters are not variable length without size and with null value. param.Validate(i, isCommandProc); @@ -9021,7 +9031,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo if (mt.IsNewKatmaiType) { - WriteSmiParameter(param, i, 0 != (options & TdsEnums.RPC_PARAM_DEFAULT), stateObj, isAdvancedTraceOn); + WriteSmiParameter(param, i, 0 != (options & TdsEnums.RPC_PARAM_DEFAULT), stateObj, enableOptimizedParameterBinding, isAdvancedTraceOn); continue; } @@ -9031,7 +9041,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo throw ADP.VersionDoesNotSupportDataType(mt.TypeName); } - Task writeParamTask = TDSExecuteRPCAddParameter(stateObj, param, mt, options, cmd); + Task writeParamTask = TDSExecuteRPCAddParameter(stateObj, param, mt, options, cmd, enableOptimizedParameterBinding); if (!sync) { @@ -9148,7 +9158,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo } } - private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParameter param, MetaType mt, byte options, SqlCommand command) + private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParameter param, MetaType mt, byte options, SqlCommand command, bool isAnonymous) { int tempLen; object value = null; @@ -9173,7 +9183,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet } } - WriteParameterName(param.ParameterNameFixed, stateObj); + WriteParameterName(param.ParameterNameFixed, stateObj, isAnonymous); // Write parameter status stateObj.WriteByte(options); @@ -9695,11 +9705,11 @@ private void ExecuteFlushTaskCallback(Task tsk, TdsParserStateObject stateObj, T } - private void WriteParameterName(string parameterName, TdsParserStateObject stateObj) + private void WriteParameterName(string parameterName, TdsParserStateObject stateObj, bool isAnonymous) { // paramLen // paramName - if (!string.IsNullOrEmpty(parameterName)) + if (!isAnonymous && !string.IsNullOrEmpty(parameterName)) { Debug.Assert(parameterName.Length <= 0xff, "parameter name can only be 255 bytes, shouldn't get to TdsParser!"); int tempLen = parameterName.Length & 0xff; @@ -9712,7 +9722,7 @@ private void WriteParameterName(string parameterName, TdsParserStateObject state } } - private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefault, TdsParserStateObject stateObj, bool advancedTraceIsOn) + private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefault, TdsParserStateObject stateObj, bool isAnonymous, bool advancedTraceIsOn) { // // Determine Metadata @@ -9771,7 +9781,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa // // Write parameter metadata // - WriteSmiParameterMetaData(metaData, sendDefault, stateObj); + WriteSmiParameterMetaData(metaData, sendDefault, isAnonymous, stateObj); // // Now write the value @@ -9790,7 +9800,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa } // Writes metadata portion of parameter stream from an SmiParameterMetaData object. - private void WriteSmiParameterMetaData(SmiParameterMetaData metaData, bool sendDefault, TdsParserStateObject stateObj) + private void WriteSmiParameterMetaData(SmiParameterMetaData metaData, bool sendDefault, bool isAnonymous, TdsParserStateObject stateObj) { // Determine status byte status = 0; @@ -9805,7 +9815,7 @@ private void WriteSmiParameterMetaData(SmiParameterMetaData metaData, bool sendD } // Write everything out - WriteParameterName(metaData.Name, stateObj); + WriteParameterName(metaData.Name, stateObj, isAnonymous); stateObj.WriteByte(status); WriteSmiTypeInfo(metaData, stateObj); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs index e043593408..5f81a9cbcc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs @@ -2931,6 +2931,15 @@ internal static string SQL_ParameterCannotBeEmpty { } } + /// + /// Looks up a localized string similar to Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command.. + /// + internal static string SQL_ParameterDirectionInvalidForOptimizedBinding { + get { + return ResourceManager.GetString("SQL_ParameterDirectionInvalidForOptimizedBinding", resourceCulture); + } + } + /// /// Looks up a localized string similar to Parameter '{0}' exceeds the size limit for the sql_variant datatype.. /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx index 14a70b74a3..4dff81e444 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx @@ -1932,4 +1932,7 @@ '{0}' is not less than '{1}'; '{2}' cannot be greater than '{3}'. + + Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index 93adb4b621..37701b072f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -556,6 +556,8 @@ public SqlCommand(string cmdText, Microsoft.Data.SqlClient.SqlConnection connect [System.ComponentModel.DesignOnlyAttribute(true)] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool DesignTimeVisible { get { throw null; } set { } } + /// + public bool EnableOptimizedParameterBinding { get { throw null; } set { } } /// public new Microsoft.Data.SqlClient.SqlParameterCollection Parameters { get { throw null; } } /// 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 2b29081c64..ea04cc1789 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 @@ -880,6 +880,9 @@ public override bool DesignTimeVisible } } + /// + public bool EnableOptimizedParameterBinding { get; set; } + /// [ DesignerSerializationVisibility(DesignerSerializationVisibility.Content), diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs index 27d0df8be2..353b85fd03 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -643,6 +643,11 @@ static internal Exception ParameterCannotBeEmpty(string paramName) return ADP.ArgumentNull(StringsHelper.GetString(Strings.SQL_ParameterCannotBeEmpty, paramName)); } + internal static Exception ParameterDirectionInvalidForOptimizedBinding(string paramName) + { + return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ParameterDirectionInvalidForOptimizedBinding, paramName)); + } + static internal Exception ActiveDirectoryInteractiveTimeout() { return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_Interactive_Authentication); 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 92482d9e83..14822bedf2 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 @@ -9880,6 +9880,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo SqlParameter[] parameters = rpcext.parameters; bool isAdvancedTraceOn = SqlClientEventSource.Log.IsAdvancedTraceOn(); + bool enableOptimizedParameterBinding = cmd.EnableOptimizedParameterBinding; for (int i = (ii == startRpc) ? startParam : 0; i < parameters.Length; i++) { @@ -9888,7 +9889,11 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo SqlParameter param = parameters[i]; // 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 + } + + ParameterDirection parameterDirection = param.Direction; // Throw an exception if ForceColumnEncryption is set on a parameter and the ColumnEncryption is not enabled on SqlConnection or SqlCommand if (param.ForceColumnEncryption && @@ -9900,12 +9905,17 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo // Check if the applications wants to force column encryption to avoid sending sensitive data to server if (param.ForceColumnEncryption && param.CipherMetadata == null - && (param.Direction == ParameterDirection.Input || param.Direction == ParameterDirection.InputOutput)) + && (parameterDirection == ParameterDirection.Input || parameterDirection == ParameterDirection.InputOutput)) { // Application wants a parameter to be encrypted before sending it to server, however server doesnt think this parameter needs encryption. throw SQL.ParamUnExpectedEncryptionMetadata(param.ParameterName, rpcext.GetCommandTextOrRpcName()); } + if (enableOptimizedParameterBinding && (parameterDirection == ParameterDirection.Output || parameterDirection == ParameterDirection.InputOutput)) + { + throw SQL.ParameterDirectionInvalidForOptimizedBinding(param.ParameterName); + } + // Validate parameters are not variable length without size and with null value. MDAC 66522 param.Validate(i, isCommandProc); @@ -9914,7 +9924,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, isAdvancedTraceOn); + WriteSmiParameter(param, i, 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_DEFAULT), stateObj, enableOptimizedParameterBinding, isAdvancedTraceOn); continue; } @@ -9929,7 +9939,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo 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) + if (parameterDirection == 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; @@ -9946,7 +9956,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo } } - WriteParameterName(param.ParameterNameFixed, stateObj); + WriteParameterName(param.ParameterNameFixed, stateObj, enableOptimizedParameterBinding); // Write parameter status stateObj.WriteByte(rpcext.paramoptions[i]); @@ -10628,11 +10638,11 @@ private void ExecuteFlushTaskCallback(Task tsk, TdsParserStateObject stateObj, T } - private void WriteParameterName(string parameterName, TdsParserStateObject stateObj) + private void WriteParameterName(string parameterName, TdsParserStateObject stateObj, bool isAnonymous) { // paramLen // paramName - if (!ADP.IsEmpty(parameterName)) + if (!isAnonymous && !string.IsNullOrEmpty(parameterName)) { Debug.Assert(parameterName.Length <= 0xff, "parameter name can only be 255 bytes, shouldn't get to TdsParser!"); int tempLen = parameterName.Length & 0xff; @@ -10646,7 +10656,7 @@ private void WriteParameterName(string parameterName, TdsParserStateObject state } private static readonly IEnumerable __tvpEmptyValue = new List().AsReadOnly(); - private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefault, TdsParserStateObject stateObj, bool advancedTraceIsOn) + private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefault, TdsParserStateObject stateObj, bool isAnonymous, bool advancedTraceIsOn) { // // Determine Metadata @@ -10706,7 +10716,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa // // Write parameter metadata // - WriteSmiParameterMetaData(metaData, sendDefault, stateObj); + WriteSmiParameterMetaData(metaData, sendDefault, isAnonymous, stateObj); // // Now write the value @@ -10725,7 +10735,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa } // Writes metadata portion of parameter stream from an SmiParameterMetaData object. - private void WriteSmiParameterMetaData(SmiParameterMetaData metaData, bool sendDefault, TdsParserStateObject stateObj) + private void WriteSmiParameterMetaData(SmiParameterMetaData metaData, bool sendDefault, bool isAnonymous, TdsParserStateObject stateObj) { // Determine status byte status = 0; @@ -10740,7 +10750,7 @@ private void WriteSmiParameterMetaData(SmiParameterMetaData metaData, bool sendD } // Write everything out - WriteParameterName(metaData.Name, stateObj); + WriteParameterName(metaData.Name, stateObj, isAnonymous); stateObj.WriteByte(status); WriteSmiTypeInfo(metaData, stateObj); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs index bf23167d40..7289e36f6b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs @@ -1824,15 +1824,6 @@ internal static string ADP_VersionDoesNotSupportDataType { } } - /// - /// Looks up a localized string similar to The validation of an attestation token failed. The token signature does not match the signature omputed using a public key retrieved from the attestation public key endpoint at '{0}'. Verify the DNS apping for the endpoint - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details. If correct, contact Customer Support Services.. - /// - internal static string AttestationTokenSignatureValidationFailed { - get { - return ResourceManager.GetString("AttestationTokenSignatureValidationFailed", resourceCulture); - } - } - /// /// Looks up a localized string similar to Destination array is not long enough to copy all the items in the collection. Check array index and length.. /// @@ -1869,6 +1860,15 @@ internal static string ArgumentOutOfRange_NeedNonNegNum { } } + /// + /// Looks up a localized string similar to The validation of an attestation token failed. The token signature does not match the signature omputed using a public key retrieved from the attestation public key endpoint at '{0}'. Verify the DNS apping for the endpoint - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details. If correct, contact Customer Support Services.. + /// + internal static string AttestationTokenSignatureValidationFailed { + get { + return ResourceManager.GetString("AttestationTokenSignatureValidationFailed", resourceCulture); + } + } + /// /// Looks up a localized string similar to .database.chinacloudapi.cn. /// @@ -9639,15 +9639,6 @@ internal static string SQL_InvalidUdt3PartNameFormat { } } - /// - /// Looks up a localized string similar to Cannot use 'Authentication={0}' with 'Password' or 'PWD' connection string keywords.. - /// - internal static string SQL_NonInteractiveWithPassword { - get { - return ResourceManager.GetString("SQL_NonInteractiveWithPassword", resourceCulture); - } - } - /// /// Looks up a localized string similar to The connection does not support MultipleActiveResultSets.. /// @@ -9720,6 +9711,15 @@ internal static string SQL_NonCharColumn { } } + /// + /// Looks up a localized string similar to Cannot use 'Authentication={0}' with 'Password' or 'PWD' connection string keywords.. + /// + internal static string SQL_NonInteractiveWithPassword { + get { + return ResourceManager.GetString("SQL_NonInteractiveWithPassword", resourceCulture); + } + } + /// /// Looks up a localized string similar to SSE Instance re-direction is not supported for non-local user instances.. /// @@ -9819,6 +9819,15 @@ internal static string SQL_ParameterCannotBeEmpty { } } + /// + /// Looks up a localized string similar to Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command.. + /// + internal static string SQL_ParameterDirectionInvalidForOptimizedBinding { + get { + return ResourceManager.GetString("SQL_ParameterDirectionInvalidForOptimizedBinding", resourceCulture); + } + } + /// /// Looks up a localized string similar to Parameter '{0}' exceeds the size limit for the sql_variant datatype.. /// @@ -12077,12 +12086,6 @@ internal static string TCE_DbConnectionString_AttestationProtocol { return ResourceManager.GetString("TCE_DbConnectionString_AttestationProtocol", resourceCulture); } } - - /// - /// Looks up a localized string similar to Specifies an IP address preference when connecting to SQL instances. - /// - internal static string TCE_DbConnectionString_IPAddressPreference - => ResourceManager.GetString("TCE_DbConnectionString_IPAddressPreference", resourceCulture); /// /// Looks up a localized string similar to Default column encryption setting for all the commands on the connection.. @@ -12102,6 +12105,15 @@ internal static string TCE_DbConnectionString_EnclaveAttestationUrl { } } + /// + /// Looks up a localized string similar to Specifies an IP address preference when connecting to SQL instances.. + /// + internal static string TCE_DbConnectionString_IPAddressPreference { + get { + return ResourceManager.GetString("TCE_DbConnectionString_IPAddressPreference", resourceCulture); + } + } + /// /// Looks up a localized string similar to Decryption failed. The last 10 bytes of the encrypted column encryption key are: '{0}'. The first 10 bytes of ciphertext are: '{1}'.. /// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx index c11ae82496..5246f97349 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx @@ -4614,4 +4614,7 @@ Non-negative number required. - + + Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command. + + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index b179b82d6a..3c55b079e5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -520,5 +520,230 @@ private enum MyEnum A = 1, B = 2 } + + private static void ExecuteNonQueryCommand(string connectionString, string cmdText) + { + using (SqlConnection conn = new SqlConnection(connectionString)) + using (SqlCommand cmd = conn.CreateCommand()) + { + conn.Open(); + cmd.CommandText = cmdText; + cmd.ExecuteNonQuery(); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + private static void EnableOptimizedParameterBinding_ParametersAreUsedByName() + { + int firstInput = 1; + int secondInput = 2; + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + + using (var command = new SqlCommand("SELECT @Second, @First", connection)) + { + command.EnableOptimizedParameterBinding = true; + command.Parameters.AddWithValue("@First", firstInput); + command.Parameters.AddWithValue("@Second", secondInput); + + using (SqlDataReader reader = command.ExecuteReader()) + { + reader.Read(); + + int firstOutput = reader.GetInt32(0); + int secondOutput = reader.GetInt32(1); + + Assert.Equal(firstInput, secondOutput); + Assert.Equal(secondInput, firstOutput); + } + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + private static void EnableOptimizedParameterBinding_NamesMustMatch() + { + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + + using (var command = new SqlCommand("SELECT @DoesNotExist", connection)) + { + command.EnableOptimizedParameterBinding = true; + command.Parameters.AddWithValue("@Exists", 1); + + SqlException sqlException = null; + try + { + command.ExecuteNonQuery(); + } + catch (SqlException sqlEx) + { + sqlException = sqlEx; + } + + Assert.NotNull(sqlException); + Assert.Contains("Must declare the scalar variable",sqlException.Message); + Assert.Contains("@DoesNotExist", sqlException.Message); + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + private static void EnableOptimizedParameterBinding_AllNamesMustBeDeclared() + { + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + + using (var command = new SqlCommand("SELECT @Exists, @DoesNotExist", connection)) + { + command.EnableOptimizedParameterBinding = true; + command.Parameters.AddWithValue("@Exists", 1); + + SqlException sqlException = null; + try + { + command.ExecuteNonQuery(); + } + catch (SqlException sqlEx) + { + sqlException = sqlEx; + } + + Assert.NotNull(sqlException); + Assert.Contains("Must declare the scalar variable", sqlException.Message); + Assert.Contains("@DoesNotExist", sqlException.Message); + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + private static void EnableOptimizedParameterBinding_NamesCanBeReUsed() + { + int firstInput = 1; + int secondInput = 2; + int thirdInput = 3; + + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + + using (var command = new SqlCommand("SELECT @First, @Second, @First", connection)) + { + command.EnableOptimizedParameterBinding = true; + command.Parameters.AddWithValue("@First", firstInput); + command.Parameters.AddWithValue("@Second", secondInput); + command.Parameters.AddWithValue("@Third", thirdInput); + + using (SqlDataReader reader = command.ExecuteReader()) + { + reader.Read(); + + int firstOutput = reader.GetInt32(0); + int secondOutput = reader.GetInt32(1); + int thirdOutput = reader.GetInt32(2); + + Assert.Equal(firstInput, firstOutput); + Assert.Equal(secondInput, secondOutput); + Assert.Equal(firstInput, thirdOutput); + } + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + private static void EnableOptimizedParameterBinding_InputOutputFails() + { + int firstInput = 1; + int secondInput = 2; + int thirdInput = 3; + + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + + using (var command = new SqlCommand("SELECT @Third = (@Third + @First + @Second)", connection)) + { + command.EnableOptimizedParameterBinding = true; + command.Parameters.AddWithValue("@First", firstInput); + command.Parameters.AddWithValue("@Second", secondInput); + SqlParameter thirdParameter = command.Parameters.AddWithValue("@Third", thirdInput); + thirdParameter.Direction = ParameterDirection.InputOutput; + + InvalidOperationException exception = Assert.Throws(() => command.ExecuteNonQuery()); + + Assert.Contains("OptimizedParameterBinding", exception.Message); + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + private static void EnableOptimizedParameterBinding_OutputFails() + { + int firstInput = 1; + int secondInput = 2; + int thirdInput = 3; + + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + + using (var command = new SqlCommand("SELECT @Third = (@Third + @First + @Second)", connection)) + { + command.EnableOptimizedParameterBinding = true; + command.Parameters.AddWithValue("@First", firstInput); + command.Parameters.AddWithValue("@Second", secondInput); + SqlParameter thirdParameter = command.Parameters.AddWithValue("@Third", thirdInput); + thirdParameter.Direction = ParameterDirection.Output; + + InvalidOperationException exception = Assert.Throws(() => command.ExecuteNonQuery()); + + Assert.Contains("OptimizedParameterBinding", exception.Message); + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + private static void EnableOptimizedParameterBinding_ReturnSucceeds() + { + int firstInput = 12; + + string sprocName = DataTestUtility.GetUniqueName("P"); + // input, output + string createSprocQuery = + "CREATE PROCEDURE " + sprocName + " @in int " + + "AS " + + "RETURN(@in)"; + + string dropSprocQuery = "DROP PROCEDURE " + sprocName; + + try + { + ExecuteNonQueryCommand(DataTestUtility.TCPConnectionString, createSprocQuery); + + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + + using (var command = new SqlCommand(sprocName, connection) { CommandType = CommandType.StoredProcedure }) + { + command.EnableOptimizedParameterBinding = true; + command.Parameters.AddWithValue("@in", firstInput); + SqlParameter returnParameter = command.Parameters.AddWithValue("@retval", 0); + returnParameter.Direction = ParameterDirection.ReturnValue; + + command.ExecuteNonQuery(); + + Assert.Equal(firstInput, Convert.ToInt32(returnParameter.Value)); + } + } + } + finally + { + ExecuteNonQueryCommand(DataTestUtility.TCPConnectionString, dropSprocQuery); + } + } } }