Skip to content

Commit d8d21ae

Browse files
authored
Fix | TDS RPC error on large queries in SqlCommand.ExecuteReaderAsync (#1936) (#1986)
1 parent af6b1c1 commit d8d21ae

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9868,10 +9868,10 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
98689868

98699869
// Options
98709870
WriteShort((short)rpcext.options, stateObj);
9871-
}
98729871

9873-
byte[] enclavePackage = cmd.enclavePackage != null ? cmd.enclavePackage.EnclavePackageBytes : null;
9874-
WriteEnclaveInfo(stateObj, enclavePackage);
9872+
byte[] enclavePackage = cmd.enclavePackage != null ? cmd.enclavePackage.EnclavePackageBytes : null;
9873+
WriteEnclaveInfo(stateObj, enclavePackage);
9874+
}
98759875

98769876
// Stream out parameters
98779877
SqlParameter[] parameters = rpcext.parameters;

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

Lines changed: 123 additions & 0 deletions
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.ManualTesting.Tests.AlwaysEncrypted.Setup;
@@ -728,6 +729,59 @@ public void TestExecuteReader(string connection)
728729
});
729730
}
730731

732+
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))]
733+
[ClassData(typeof(AEConnectionStringProvider))]
734+
public async void TestExecuteReaderAsyncWithLargeQuery(string connectionString)
735+
{
736+
string randomName = DataTestUtility.GetUniqueName(Guid.NewGuid().ToString().Replace("-", ""), false);
737+
if (randomName.Length > 50)
738+
{
739+
randomName = randomName.Substring(0, 50);
740+
}
741+
string tableName = $"VeryLong_{randomName}_TestTableName";
742+
int columnsCount = 50;
743+
744+
// Arrange - drops the table with long name and re-creates it with 52 columns (ID, name, ColumnName0..49)
745+
try
746+
{
747+
CreateTable(connectionString, tableName, columnsCount);
748+
string name = "nobody";
749+
750+
using (SqlConnection connection = new SqlConnection(connectionString))
751+
{
752+
await connection.OpenAsync();
753+
// This creates a "select top 100" query that has over 40k characters
754+
using (SqlCommand sqlCommand = new SqlCommand(GenerateSelectQuery(tableName, columnsCount, 10, "WHERE Name = @FirstName AND ID = @CustomerId"),
755+
connection,
756+
transaction: null,
757+
columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled))
758+
{
759+
sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int);
760+
sqlCommand.Parameters.Add(@"FirstName", SqlDbType.VarChar, name.Length);
761+
762+
sqlCommand.Parameters[0].Value = 0;
763+
sqlCommand.Parameters[1].Value = name;
764+
765+
// Act and Assert
766+
// Test that execute reader async does not throw an exception.
767+
// 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.
768+
using (SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync())
769+
{
770+
Assert.False(sqlDataReader.HasRows, "The table should be empty");
771+
}
772+
}
773+
}
774+
}
775+
catch
776+
{
777+
throw;
778+
}
779+
finally
780+
{
781+
DropTableIfExists(connectionString, tableName);
782+
}
783+
}
784+
731785
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))]
732786
[ClassData(typeof(AEConnectionStringProviderWithCommandBehaviorSet1))]
733787
public void TestExecuteReaderWithCommandBehavior(string connection, CommandBehavior commandBehavior)
@@ -2552,6 +2606,75 @@ private void CleanUpTable(string connString, string tableName)
25522606
}
25532607
}
25542608

2609+
private static void CreateTable(string connString, string tableName, int columnsCount)
2610+
=> DataTestUtility.RunNonQuery(connString, GenerateCreateQuery(tableName, columnsCount));
2611+
/// <summary>
2612+
/// Drops the table if the specified table exists
2613+
/// </summary>
2614+
/// <param name="connString">The connection string to the database</param>
2615+
/// <param name="tableName">The name of the table to be dropped</param>
2616+
private static void DropTableIfExists(string connString, string tableName)
2617+
{
2618+
using var sqlConnection = new SqlConnection(connString);
2619+
sqlConnection.Open();
2620+
DataTestUtility.DropTable(sqlConnection, tableName);
2621+
}
2622+
2623+
/// <summary>
2624+
/// Generates the query for creating a table with the number of bit columns specified.
2625+
/// </summary>
2626+
/// <param name="tableName">The name of the table</param>
2627+
/// <param name="columnsCount">The number of columns for the table</param>
2628+
/// <returns></returns>
2629+
private static string GenerateCreateQuery(string tableName, int columnsCount)
2630+
{
2631+
StringBuilder builder = new StringBuilder();
2632+
builder.Append(string.Format("CREATE TABLE [dbo].[{0}]", tableName));
2633+
builder.Append('(');
2634+
builder.AppendLine("[ID][bigint] NOT NULL,");
2635+
builder.AppendLine("[Name] [varchar] (200) NOT NULL");
2636+
for (int i = 0; i < columnsCount; i++)
2637+
{
2638+
builder.Append(',');
2639+
builder.Append($"[ColumnName{i}][bit] NULL");
2640+
}
2641+
builder.Append(");");
2642+
2643+
return builder.ToString();
2644+
}
2645+
2646+
/// <summary>
2647+
/// Generates the large query with the select top 100 of all the columns repeated multiple times.
2648+
/// </summary>
2649+
/// <param name="tableName">The name of the table</param>
2650+
/// <param name="columnsCount">The number of columns to be explicitly included</param>
2651+
/// <param name="repeat">The number of times the select query is repeated</param>
2652+
/// <param name="where">A where clause for additional filters</param>
2653+
/// <returns></returns>
2654+
private static string GenerateSelectQuery(string tableName, int columnsCount, int repeat = 10, string where = "")
2655+
{
2656+
StringBuilder builder = new StringBuilder();
2657+
builder.AppendLine($"SELECT TOP 100");
2658+
builder.AppendLine($"[{tableName}].[ID],");
2659+
builder.AppendLine($"[{tableName}].[Name]");
2660+
for (int i = 0; i < columnsCount; i++)
2661+
{
2662+
builder.Append(",");
2663+
builder.AppendLine($"[{tableName}].[ColumnName{i}]");
2664+
}
2665+
2666+
string extra = string.IsNullOrEmpty(where) ? $"(NOLOCK) [{tableName}]" : where;
2667+
builder.AppendLine($"FROM [{tableName}] {extra};");
2668+
2669+
StringBuilder builder2 = new StringBuilder();
2670+
for (int i = 0; i < repeat; i++)
2671+
{
2672+
builder2.AppendLine(builder.ToString());
2673+
}
2674+
2675+
return builder2.ToString();
2676+
}
2677+
25552678
/// <summary>
25562679
/// An helper method to test the cancellation of the command using cancellationToken to async SqlCommand APIs.
25572680
/// </summary>

0 commit comments

Comments
 (0)