Skip to content

Commit 396ecc8

Browse files
committed
Fix | TDS RPC error on large queries in SqlCommand.ExecuteReaderAsync (dotnet#1936)
1 parent 8768323 commit 396ecc8

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
@@ -9907,10 +9907,10 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo
99079907

99089908
// Options
99099909
WriteShort((short)rpcext.options, stateObj);
9910-
}
99119910

9912-
byte[] enclavePackage = cmd.enclavePackage != null ? cmd.enclavePackage.EnclavePackageBytes : null;
9913-
WriteEnclaveInfo(stateObj, enclavePackage);
9911+
byte[] enclavePackage = cmd.enclavePackage != null ? cmd.enclavePackage.EnclavePackageBytes : null;
9912+
WriteEnclaveInfo(stateObj, enclavePackage);
9913+
}
99149914

99159915
// Stream out parameters
99169916
SqlParameter[] parameters = rpcext.parameters;

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)
@@ -2788,6 +2842,75 @@ private void CleanUpTable(string connString, string tableName)
27882842
}
27892843
}
27902844

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

0 commit comments

Comments
 (0)