Skip to content

Commit fffcb08

Browse files
[Hotfix 2.1.5] | Fix CommandText length for stored procedures (#1484) (#1726)
1 parent 5f638d5 commit fffcb08

File tree

5 files changed

+72
-4
lines changed

5 files changed

+72
-4
lines changed

src/Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project ToolsVersion="Current" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<PropertyGroup>
4+
<LangVersion>9.0</LangVersion>
45
<TargetsWindows Condition="'$(OS)' == 'Windows_NT' AND '$(OSGroup)' == ''">true</TargetsWindows>
56
<TargetsWindows Condition="'$(OS)' != 'Windows_NT' AND '$(OSGroup)' == ''">false</TargetsWindows>
67
<TargetsWindows Condition="'$(OSGroup)' == 'Windows_NT'">true</TargetsWindows>

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace Microsoft.Data.SqlClient
2626
public sealed partial class SqlCommand : DbCommand, ICloneable
2727
{
2828
private static int _objectTypeCount; // EventSource Counter
29+
private const int MaxRPCNameLength = 1046;
2930
internal readonly int ObjectID = Interlocked.Increment(ref _objectTypeCount); private string _commandText;
3031

3132
private static readonly Func<AsyncCallback, object, IAsyncResult> s_beginExecuteReaderAsync = BeginExecuteReaderAsyncCallback;
@@ -5595,7 +5596,20 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql
55955596
GetRPCObject(0, userParameterCount, ref rpc);
55965597

55975598
rpc.ProcID = 0;
5598-
rpc.rpcName = this.CommandText; // just get the raw command text
5599+
5600+
// TDS Protocol allows rpc name with maximum length of 1046 bytes for ProcName
5601+
// 4-part name 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 = 523
5602+
// each char takes 2 bytes. 523 * 2 = 1046
5603+
int commandTextLength = ADP.CharSize * CommandText.Length;
5604+
5605+
if (commandTextLength <= MaxRPCNameLength)
5606+
{
5607+
rpc.rpcName = CommandText; // just get the raw command text
5608+
}
5609+
else
5610+
{
5611+
throw ADP.InvalidArgumentLength(nameof(CommandText), MaxRPCNameLength);
5612+
}
55995613

56005614
SetUpRPCParameters(rpc, inSchema, parameters);
56015615
}

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ namespace Microsoft.Data.SqlClient
3535
public sealed class SqlCommand : DbCommand, ICloneable
3636
{
3737
private static int _objectTypeCount; // EventSource Counter
38+
private const int MaxRPCNameLength = 1046;
3839
internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
3940

4041
private string _commandText;
@@ -1053,7 +1054,7 @@ override public void Prepare()
10531054
{
10541055
tdsReliabilitySection.Start();
10551056
#else
1056-
{
1057+
{
10571058
#endif //DEBUG
10581059
InternalPrepare();
10591060
}
@@ -1239,7 +1240,7 @@ override public void Cancel()
12391240
{
12401241
tdsReliabilitySection.Start();
12411242
#else
1242-
{
1243+
{
12431244
#endif //DEBUG
12441245
bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);
12451246

@@ -6570,7 +6571,19 @@ private void BuildRPC(bool inSchema, SqlParameterCollection parameters, ref _Sql
65706571
int count = CountSendableParameters(parameters);
65716572
GetRPCObject(count, ref rpc);
65726573

6573-
rpc.rpcName = this.CommandText; // just get the raw command text
6574+
// TDS Protocol allows rpc name with maximum length of 1046 bytes for ProcName
6575+
// 4-part name 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 = 523
6576+
// each char takes 2 bytes. 523 * 2 = 1046
6577+
int commandTextLength = ADP.CharSize * CommandText.Length;
6578+
6579+
if (commandTextLength <= MaxRPCNameLength)
6580+
{
6581+
rpc.rpcName = CommandText; // just get the raw command text
6582+
}
6583+
else
6584+
{
6585+
throw ADP.InvalidArgumentLength(nameof(CommandText), MaxRPCNameLength);
6586+
}
65746587

65756588
SetUpRPCParameters(rpc, 0, inSchema, parameters);
65766589
}

src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
<Compile Include="SQL\ParallelTransactionsTest\ParallelTransactionsTest.cs" />
127127
<Compile Include="SQL\SqlBulkCopyTest\CopyWidenNullInexactNumerics.cs" />
128128
<Compile Include="SQL\SqlCommand\SqlCommandSetTest.cs" />
129+
<Compile Include="SQL\SqlCommand\SqlCommandStoredProcTest.cs" />
129130
<Compile Include="SQL\SqlCredentialTest\SqlCredentialTest.cs" />
130131
<Compile Include="SQL\SqlDependencyTest\SqlDependencyTest.cs" />
131132
<Compile Include="SQL\SqlSchemaInfoTest\SqlSchemaInfoTest.cs" />
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Data;
7+
using Xunit;
8+
9+
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
10+
{
11+
public class SqlCommandStoredProcTest
12+
{
13+
private static readonly string s_tcp_connStr = DataTestUtility.TCPConnectionString;
14+
15+
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
16+
public static void ShouldFailWithExceededLengthForSP()
17+
{
18+
string baseCommandText = "random text\u0000\u400a\u7300\u7400\u6100\u7400\u6500\u6d00\u6500\u6e00\u7400\u0000\u0006\u01ff\u0900\uf004\u0000\uffdc\u0001";
19+
string exceededLengthText = baseCommandText + new string(' ', 2000);
20+
using SqlConnection conn = new(s_tcp_connStr);
21+
conn.Open();
22+
using SqlCommand command = new()
23+
{
24+
Connection = conn,
25+
CommandType = CommandType.StoredProcedure,
26+
CommandText = exceededLengthText
27+
};
28+
29+
// It should fail on the driver as the length of RPC is over 1046
30+
// 4-part name 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 + 1 + 1 + 128 + 1 = 523
31+
// each char takes 2 bytes. 523 * 2 = 1046
32+
Assert.Throws<ArgumentException>(() => command.ExecuteScalar());
33+
34+
command.CommandText = baseCommandText;
35+
var ex = Assert.Throws<SqlException>(() => command.ExecuteScalar());
36+
Assert.StartsWith("Could not find stored procedure", ex.Message);
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)