Skip to content

Commit 41af66f

Browse files
authored
[5.1.1] Fix | TDS RPC error on large queries in SqlCommand.ExecuteReaderAsync (#1965)
1 parent f70b9c6 commit 41af66f

File tree

2 files changed

+126
-3
lines changed

2 files changed

+126
-3
lines changed

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -10024,10 +10024,10 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
1002410024

1002510025
// Options
1002610026
WriteShort((short)rpcext.options, stateObj);
10027-
}
1002810027

10029-
byte[] enclavePackage = cmd.enclavePackage != null ? cmd.enclavePackage.EnclavePackageBytes : null;
10030-
WriteEnclaveInfo(stateObj, enclavePackage);
10028+
byte[] enclavePackage = cmd.enclavePackage != null ? cmd.enclavePackage.EnclavePackageBytes : null;
10029+
WriteEnclaveInfo(stateObj, enclavePackage);
10030+
}
1003110031

1003210032
// Stream out parameters
1003310033
int parametersLength = rpcext.userParamCount + rpcext.systemParamCount;

src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs

+123
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.IO;
99
using System.Linq;
1010
using System.Reflection;
11+
using System.Text;
1112
using System.Threading;
1213
using System.Threading.Tasks;
1314
using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;
@@ -689,6 +690,59 @@ public void TestExecuteReader(string connection)
689690
});
690691
}
691692

693+
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))]
694+
[ClassData(typeof(AEConnectionStringProvider))]
695+
public async void TestExecuteReaderAsyncWithLargeQuery(string connectionString)
696+
{
697+
string randomName = DataTestUtility.GetUniqueName(Guid.NewGuid().ToString().Replace("-", ""), false);
698+
if (randomName.Length > 50)
699+
{
700+
randomName = randomName.Substring(0, 50);
701+
}
702+
string tableName = $"VeryLong_{randomName}_TestTableName";
703+
int columnsCount = 50;
704+
705+
// Arrange - drops the table with long name and re-creates it with 52 columns (ID, name, ColumnName0..49)
706+
try
707+
{
708+
CreateTable(connectionString, tableName, columnsCount);
709+
string name = "nobody";
710+
711+
using (SqlConnection connection = new SqlConnection(connectionString))
712+
{
713+
await connection.OpenAsync();
714+
// This creates a "select top 100" query that has over 40k characters
715+
using (SqlCommand sqlCommand = new SqlCommand(GenerateSelectQuery(tableName, columnsCount, 10, "WHERE Name = @FirstName AND ID = @CustomerId"),
716+
connection,
717+
transaction: null,
718+
columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled))
719+
{
720+
sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int);
721+
sqlCommand.Parameters.Add(@"FirstName", SqlDbType.VarChar, name.Length);
722+
723+
sqlCommand.Parameters[0].Value = 0;
724+
sqlCommand.Parameters[1].Value = name;
725+
726+
// Act and Assert
727+
// Test that execute reader async does not throw an exception.
728+
// The table is empty so there should be no results; however, the bug previously found is that it causes a TDS RPC exception on enclave.
729+
using (SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync())
730+
{
731+
Assert.False(sqlDataReader.HasRows, "The table should be empty");
732+
}
733+
}
734+
}
735+
}
736+
catch
737+
{
738+
throw;
739+
}
740+
finally
741+
{
742+
DropTableIfExists(connectionString, tableName);
743+
}
744+
}
745+
692746
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))]
693747
[ClassData(typeof(AEConnectionStringProviderWithCommandBehaviorSet1))]
694748
public void TestExecuteReaderWithCommandBehavior(string connection, CommandBehavior commandBehavior)
@@ -2806,6 +2860,75 @@ private void CleanUpTable(string connString, string tableName)
28062860
}
28072861
}
28082862

2863+
private static void CreateTable(string connString, string tableName, int columnsCount)
2864+
=> DataTestUtility.RunNonQuery(connString, GenerateCreateQuery(tableName, columnsCount));
2865+
/// <summary>
2866+
/// Drops the table if the specified table exists
2867+
/// </summary>
2868+
/// <param name="connString">The connection string to the database</param>
2869+
/// <param name="tableName">The name of the table to be dropped</param>
2870+
private static void DropTableIfExists(string connString, string tableName)
2871+
{
2872+
using var sqlConnection = new SqlConnection(connString);
2873+
sqlConnection.Open();
2874+
DataTestUtility.DropTable(sqlConnection, tableName);
2875+
}
2876+
2877+
/// <summary>
2878+
/// Generates the query for creating a table with the number of bit columns specified.
2879+
/// </summary>
2880+
/// <param name="tableName">The name of the table</param>
2881+
/// <param name="columnsCount">The number of columns for the table</param>
2882+
/// <returns></returns>
2883+
private static string GenerateCreateQuery(string tableName, int columnsCount)
2884+
{
2885+
StringBuilder builder = new StringBuilder();
2886+
builder.Append(string.Format("CREATE TABLE [dbo].[{0}]", tableName));
2887+
builder.Append('(');
2888+
builder.AppendLine("[ID][bigint] NOT NULL,");
2889+
builder.AppendLine("[Name] [varchar] (200) NOT NULL");
2890+
for (int i = 0; i < columnsCount; i++)
2891+
{
2892+
builder.Append(',');
2893+
builder.Append($"[ColumnName{i}][bit] NULL");
2894+
}
2895+
builder.Append(");");
2896+
2897+
return builder.ToString();
2898+
}
2899+
2900+
/// <summary>
2901+
/// Generates the large query with the select top 100 of all the columns repeated multiple times.
2902+
/// </summary>
2903+
/// <param name="tableName">The name of the table</param>
2904+
/// <param name="columnsCount">The number of columns to be explicitly included</param>
2905+
/// <param name="repeat">The number of times the select query is repeated</param>
2906+
/// <param name="where">A where clause for additional filters</param>
2907+
/// <returns></returns>
2908+
private static string GenerateSelectQuery(string tableName, int columnsCount, int repeat = 10, string where = "")
2909+
{
2910+
StringBuilder builder = new StringBuilder();
2911+
builder.AppendLine($"SELECT TOP 100");
2912+
builder.AppendLine($"[{tableName}].[ID],");
2913+
builder.AppendLine($"[{tableName}].[Name]");
2914+
for (int i = 0; i < columnsCount; i++)
2915+
{
2916+
builder.Append(",");
2917+
builder.AppendLine($"[{tableName}].[ColumnName{i}]");
2918+
}
2919+
2920+
string extra = string.IsNullOrEmpty(where) ? $"(NOLOCK) [{tableName}]" : where;
2921+
builder.AppendLine($"FROM [{tableName}] {extra};");
2922+
2923+
StringBuilder builder2 = new StringBuilder();
2924+
for (int i = 0; i < repeat; i++)
2925+
{
2926+
builder2.AppendLine(builder.ToString());
2927+
}
2928+
2929+
return builder2.ToString();
2930+
}
2931+
28092932
/// <summary>
28102933
/// An helper method to test the cancellation of the command using cancellationToken to async SqlCommand APIs.
28112934
/// </summary>

0 commit comments

Comments
 (0)