Skip to content

Commit

Permalink
[Hotfix 2.1.5] | Fix CommandText length for stored procedures (#1484) (
Browse files Browse the repository at this point in the history
  • Loading branch information
DavoudEshtehari authored Aug 23, 2022
1 parent 5f638d5 commit fffcb08
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<LangVersion>9.0</LangVersion>
<TargetsWindows Condition="'$(OS)' == 'Windows_NT' AND '$(OSGroup)' == ''">true</TargetsWindows>
<TargetsWindows Condition="'$(OS)' != 'Windows_NT' AND '$(OSGroup)' == ''">false</TargetsWindows>
<TargetsWindows Condition="'$(OSGroup)' == 'Windows_NT'">true</TargetsWindows>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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<AsyncCallback, object, IAsyncResult> s_beginExecuteReaderAsync = BeginExecuteReaderAsyncCallback;
Expand Down Expand Up @@ -5595,7 +5596,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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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;
Expand Down Expand Up @@ -1053,7 +1054,7 @@ override public void Prepare()
{
tdsReliabilitySection.Start();
#else
{
{
#endif //DEBUG
InternalPrepare();
}
Expand Down Expand Up @@ -1239,7 +1240,7 @@ override public void Cancel()
{
tdsReliabilitySection.Start();
#else
{
{
#endif //DEBUG
bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection);

Expand Down Expand Up @@ -6570,7 +6571,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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
<Compile Include="SQL\ParallelTransactionsTest\ParallelTransactionsTest.cs" />
<Compile Include="SQL\SqlBulkCopyTest\CopyWidenNullInexactNumerics.cs" />
<Compile Include="SQL\SqlCommand\SqlCommandSetTest.cs" />
<Compile Include="SQL\SqlCommand\SqlCommandStoredProcTest.cs" />
<Compile Include="SQL\SqlCredentialTest\SqlCredentialTest.cs" />
<Compile Include="SQL\SqlDependencyTest\SqlDependencyTest.cs" />
<Compile Include="SQL\SqlSchemaInfoTest\SqlSchemaInfoTest.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ArgumentException>(() => command.ExecuteScalar());

command.CommandText = baseCommandText;
var ex = Assert.Throws<SqlException>(() => command.ExecuteScalar());
Assert.StartsWith("Could not find stored procedure", ex.Message);
}
}
}

0 comments on commit fffcb08

Please sign in to comment.