Skip to content

Commit

Permalink
[5.1.5] Fix | Enable reading AE date as DateOnly (#2275) (#2324)
Browse files Browse the repository at this point in the history
  • Loading branch information
JRahnama committed Jan 27, 2024
1 parent 759dc69 commit 01a589e
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 4 deletions.
Expand Up @@ -2844,11 +2844,11 @@ private T GetFieldValueFromSqlBufferInternal<T>(SqlBuffer data, _SqlMetaData met
return (T)(object)data.DateTime;
}
#if NET6_0_OR_GREATER
else if (typeof(T) == typeof(DateOnly) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType)
else if (typeof(T) == typeof(DateOnly) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005)
{
return (T)(object)data.DateOnly;
}
else if (typeof(T) == typeof(TimeOnly) && dataType == typeof(TimeOnly) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType)
else if (typeof(T) == typeof(TimeOnly) && dataType == typeof(TimeOnly) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005)
{
return (T)(object)data.TimeOnly;
}
Expand Down
Expand Up @@ -3169,6 +3169,10 @@ public Customer(int id, string firstName, string lastName)
public string LastName { get; set; }
}

#if NET6_0_OR_GREATER
public record CustomerDateOnly(int Id, string FirstName, string LastName, DateOnly DateOfBirth, TimeOnly TimeOfDay);
#endif

internal class TestAsyncCallBackStateObject
{
/// <summary>
Expand Down
@@ -0,0 +1,93 @@
// 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.Collections;
using System.Collections.Generic;
using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup;
using Xunit;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted
{
public sealed class DateOnlyReadTests : IClassFixture<PlatformSpecificTestContext>, IDisposable
{
private SQLSetupStrategy fixture;

private readonly string tableName;

public DateOnlyReadTests(PlatformSpecificTestContext context)
{
fixture = context.Fixture;
tableName = fixture.DateOnlyTestTable.Name;
}

// tests
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))]
[ClassData(typeof(TestSelectOnEncryptedNonEncryptedColumnsDataDateOnly))]
public void TestSelectOnEncryptedNonEncryptedColumns(string connString, string selectQuery, int totalColumnsInSelect, string[] types)
{
Assert.False(string.IsNullOrWhiteSpace(selectQuery), "FAILED: select query should not be null or empty.");
Assert.True(totalColumnsInSelect <= 3, "FAILED: totalColumnsInSelect should <= 3.");

using (SqlConnection sqlConn = new SqlConnection(connString))
{
sqlConn.Open();

Table.DeleteData(tableName, sqlConn);

// insert 1 row data
CustomerDateOnly customer = new CustomerDateOnly(
45,
"Microsoft",
"Corporation",
new DateOnly(2001, 1, 31),
new TimeOnly(18, 36, 45));

DatabaseHelper.InsertCustomerDateOnlyData(sqlConn, null, tableName, customer);

using (SqlCommand sqlCommand = new SqlCommand(string.Format(selectQuery, tableName),
sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled))
{
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
{
Assert.True(sqlDataReader.HasRows, "FAILED: Select statement did not return any rows.");

while (sqlDataReader.Read())
{
DatabaseHelper.CompareResults(sqlDataReader, types, totalColumnsInSelect);
}
}
}
}
}


public void Dispose()
{
foreach (string connStrAE in DataTestUtility.AEConnStringsSetup)
{
using (SqlConnection sqlConnection = new SqlConnection(connStrAE))
{
sqlConnection.Open();
Table.DeleteData(fixture.DateOnlyTestTable.Name, sqlConnection);
}
}
}
}

public class TestSelectOnEncryptedNonEncryptedColumnsDataDateOnly : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
foreach (string connStrAE in DataTestUtility.AEConnStrings)
{
yield return new object[] { connStrAE, @"select CustomerId, FirstName, LastName from [{0}] ", 3, new string[] { @"int", @"string", @"string" } };
yield return new object[] { connStrAE, @"select CustomerId, FirstName from [{0}] ", 2, new string[] { @"int", @"string" } };
yield return new object[] { connStrAE, @"select LastName from [{0}] ", 1, new string[] { @"string" }};
yield return new object[] { connStrAE, @"select DateOfBirth, TimeOfDay from [{0}] ", 2, new string[] { @"DateOnly", "TimeOnly" } };
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
Expand Up @@ -31,6 +31,27 @@ internal static void InsertCustomerData(SqlConnection sqlConnection, SqlTransact
sqlCommand.ExecuteNonQuery();
}

#if NET6_0_OR_GREATER
/// <summary>
/// Insert CustomerDateOnly record into table
/// </summary>
internal static void InsertCustomerDateOnlyData(SqlConnection sqlConnection, SqlTransaction transaction, string tableName, CustomerDateOnly customer)
{
using SqlCommand sqlCommand = new(
$"INSERT INTO [{tableName}] (CustomerId, FirstName, LastName, DateOfBirth, TimeOfDay) VALUES (@CustomerId, @FirstName, @LastName, @DateOfBirth, @TimeOfDay);",
connection: sqlConnection,
transaction: transaction,
columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled);

sqlCommand.Parameters.AddWithValue(@"CustomerId", customer.Id);
sqlCommand.Parameters.AddWithValue(@"FirstName", customer.FirstName);
sqlCommand.Parameters.AddWithValue(@"LastName", customer.LastName);
sqlCommand.Parameters.AddWithValue(@"DateOfBirth", customer.DateOfBirth);
sqlCommand.Parameters.AddWithValue(@"TimeOfDay", customer.TimeOfDay);
sqlCommand.ExecuteNonQuery();
}
#endif

/// <summary>
/// Validates that the results are the ones expected.
/// </summary>
Expand Down Expand Up @@ -155,7 +176,15 @@ public static void CompareResults(SqlDataReader sqlDataReader, string[] paramete
case "int":
Assert.True(sqlDataReader.GetInt32(columnsRead) == 45, "FAILED: read int value does not match.");
break;
#if NET6_0_OR_GREATER
case "DateOnly":
Assert.True(sqlDataReader.GetFieldValue<DateOnly>(columnsRead) == new DateOnly(2001, 1, 31), "FAILED: read DateOnly value does not match.");
break;

case "TimeOnly":
Assert.True(sqlDataReader.GetFieldValue<TimeOnly>(columnsRead) == new TimeOnly(18, 36, 45), "FAILED: read TimeOnly value does not match.");
break;
#endif
default:
Assert.True(false, "FAILED: unexpected data type.");
break;
Expand Down
@@ -1,4 +1,5 @@
// Licensed to the .NET Foundation under one or more agreements.

// 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;

Expand All @@ -21,6 +22,7 @@ public class SQLSetupStrategy : IDisposable
public Table BulkCopyAEErrorMessageTestTable { get; private set; }
public Table BulkCopyAETestTable { get; private set; }
public Table SqlParameterPropertiesTable { get; private set; }
public Table DateOnlyTestTable { get; private set; }
public Table End2EndSmokeTable { get; private set; }
public Table TrustedMasterKeyPathsTestTable { get; private set; }
public Table SqlNullValuesTable { get; private set; }
Expand Down Expand Up @@ -131,6 +133,9 @@ protected List<Table> CreateTables(IList<ColumnEncryptionKey> columnEncryptionKe
End2EndSmokeTable = new ApiTestTable(GenerateUniqueName("End2EndSmokeTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]);
tables.Add(End2EndSmokeTable);

DateOnlyTestTable = new DateOnlyTestTable(GenerateUniqueName("DateOnlyTestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]);
tables.Add(DateOnlyTestTable);

TrustedMasterKeyPathsTestTable = new ApiTestTable(GenerateUniqueName("TrustedMasterKeyPathsTestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]);
tables.Add(TrustedMasterKeyPathsTestTable);

Expand Down
@@ -0,0 +1,42 @@
// 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.

namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup
{
public class DateOnlyTestTable : Table
{
private const string ColumnEncryptionAlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA_256";
public ColumnEncryptionKey columnEncryptionKey1;
public ColumnEncryptionKey columnEncryptionKey2;
private bool useDeterministicEncryption;

public DateOnlyTestTable(string tableName, ColumnEncryptionKey columnEncryptionKey1, ColumnEncryptionKey columnEncryptionKey2, bool useDeterministicEncryption = false) : base(tableName)
{
this.columnEncryptionKey1 = columnEncryptionKey1;
this.columnEncryptionKey2 = columnEncryptionKey2;
this.useDeterministicEncryption = useDeterministicEncryption;
}

public override void Create(SqlConnection sqlConnection)
{
string encryptionType = useDeterministicEncryption ? "DETERMINISTIC" : DataTestUtility.EnclaveEnabled ? "RANDOMIZED" : "DETERMINISTIC";
string sql =
$@"CREATE TABLE [dbo].[{Name}]
(
[CustomerId] [int] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey1.Name}], ENCRYPTION_TYPE = {encryptionType}, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
[FirstName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey2.Name}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
[LastName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey2.Name}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
[TimeOfDay] [time] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey1.Name}], ENCRYPTION_TYPE = RANDOMIZED, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
[DateOfBirth] [date] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey1.Name}], ENCRYPTION_TYPE = RANDOMIZED, ALGORITHM = '{ColumnEncryptionAlgorithmName}')
)";

using (SqlCommand command = sqlConnection.CreateCommand())
{
command.CommandText = sql;
command.ExecuteNonQuery();
}
}

}
}
Expand Up @@ -61,7 +61,6 @@ public static class DataTestUtility
public static readonly bool IsDNSCachingSupportedTR = false; // this is for the tenant ring
public static readonly string UserManagedIdentityClientId = null;


public static readonly string EnclaveAzureDatabaseConnString = null;
public static bool ManagedIdentitySupported = true;
public static string AADAccessToken = null;
Expand Down
Expand Up @@ -57,6 +57,7 @@
<Compile Include="AlwaysEncrypted\TestFixtures\Setup\BulkCopyAETestTable.cs" />
<Compile Include="AlwaysEncrypted\TestFixtures\Setup\BulkCopyAEErrorMessageTestTable.cs" />
<Compile Include="AlwaysEncrypted\TestFixtures\Setup\BulkCopyTruncationTables.cs" />
<Compile Include="AlwaysEncrypted\TestFixtures\Setup\DateOnlyTestTable.cs" />
<Compile Include="AlwaysEncrypted\TestFixtures\Setup\SqlNullValuesTable.cs" />
<Compile Include="AlwaysEncrypted\TestFixtures\Setup\SqlParameterPropertiesTable.cs" />
<Compile Include="AlwaysEncrypted\TestFixtures\Setup\DbObject.cs" />
Expand All @@ -70,6 +71,9 @@
<Compile Include="AlwaysEncrypted\TestFixtures\Setup\DummyProviderMasterKey.cs" />
<Compile Include="AlwaysEncrypted\TestFixtures\Setup\CertificateUtility.cs" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0')) AND ('$(TestSet)' == '' OR '$(TestSet)' == 'AE')">
<Compile Include="AlwaysEncrypted\DateOnlyReadTests.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TestSet)' == '' OR '$(TestSet)' == '1'">
<Compile Include="SQL\AsyncTest\AsyncTimeoutTest.cs" />
<Compile Include="SQL\AsyncTest\BeginExecAsyncTest.cs" />
Expand Down

0 comments on commit 01a589e

Please sign in to comment.