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 6d14a45798..2485448ff1 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 @@ -34,6 +34,7 @@ namespace Microsoft.Data.SqlClient public sealed partial class SqlCommand : DbCommand, ICloneable { private static int _objectTypeCount; // EventSource Counter + private const int MaxRPCNameLength = 1046; internal readonly int ObjectID = Interlocked.Increment(ref _objectTypeCount); private string _commandText; private static readonly Func s_beginExecuteReaderAsync = BeginExecuteReaderAsyncCallback; @@ -5804,7 +5805,20 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql GetRPCObject(0, userParameterCount, ref rpc); rpc.ProcID = 0; - rpc.rpcName = this.CommandText; // just get the raw command text + + // 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; + + if (commandTextLength <= MaxRPCNameLength) + { + rpc.rpcName = CommandText; // just get the raw command text + } + else + { + throw ADP.InvalidArgumentLength(nameof(CommandText), MaxRPCNameLength); + } 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 183a812a7a..735d7d8f9c 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 @@ -40,6 +40,7 @@ namespace Microsoft.Data.SqlClient public sealed class SqlCommand : DbCommand, ICloneable { private static int _objectTypeCount; // EventSource Counter + private const int MaxRPCNameLength = 1046; internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); private string _commandText; @@ -1081,7 +1082,7 @@ public override void Prepare() { tdsReliabilitySection.Start(); #else - { + { #endif //DEBUG InternalPrepare(); } @@ -1266,7 +1267,7 @@ public override void Cancel() { tdsReliabilitySection.Start(); #else - { + { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); @@ -6692,7 +6693,19 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql int count = CountSendableParameters(parameters); GetRPCObject(count, ref rpc); - rpc.rpcName = this.CommandText; // just get the raw command text + // 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; + + if (commandTextLength <= MaxRPCNameLength) + { + rpc.rpcName = CommandText; // just get the raw command text + } + else + { + throw ADP.InvalidArgumentLength(nameof(CommandText), MaxRPCNameLength); + } SetUpRPCParameters(rpc, 0, inSchema, parameters); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 3364313106..aa7b439be8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -269,6 +269,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs new file mode 100644 index 0000000000..211e9c0d38 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + public class SqlCommandStoredProcTest + { + private static readonly string s_tcp_connStr = DataTestUtility.TCPConnectionString; + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public static void ShouldFailWithExceededLengthForSP() + { + string baseCommandText = "random text\u0000\u400a\u7300\u7400\u6100\u7400\u6500\u6d00\u6500\u6e00\u7400\u0000\u0006\u01ff\u0900\uf004\u0000\uffdc\u0001"; + string exceededLengthText = baseCommandText + new string(' ', 2000); + using SqlConnection conn = new(s_tcp_connStr); + conn.Open(); + using SqlCommand command = new() + { + Connection = conn, + CommandType = CommandType.StoredProcedure, + CommandText = exceededLengthText + }; + + // It should fail on the driver as the length of RPC is over 1046 + // 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 + Assert.Throws(() => command.ExecuteScalar()); + + command.CommandText = baseCommandText; + var ex = Assert.Throws(() => command.ExecuteScalar()); + Assert.StartsWith("Could not find stored procedure", ex.Message); + } + } +}