From 89134e4551ce71f9f894f1d3756e5ccfca2d3cb5 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Mon, 8 Apr 2024 10:46:01 -0700 Subject: [PATCH 01/20] Fix the DateTimeOffset parameter size in the TdsValueSetter to be included in a packet. --- .../src/Microsoft/Data/SqlClient/TdsValueSetter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index f2c33824a2..384eb20395 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -699,8 +699,8 @@ internal void SetDateTimeOffset(DateTimeOffset value) #if NETCOREAPP Span result = stackalloc byte[9]; BinaryPrimitives.WriteInt64LittleEndian(result, time); - BinaryPrimitives.WriteInt32LittleEndian(result.Slice(5), days); - _stateObj.WriteByteSpan(result.Slice(0, 8)); + BinaryPrimitives.WriteInt32LittleEndian(result.Slice(3), days); + _stateObj.WriteByteSpan(result.Slice(0, 6)); #else _stateObj.WriteByteArray(BitConverter.GetBytes(time), length - 5, 0); // time _stateObj.WriteByteArray(BitConverter.GetBytes(days), 3, 0); // date From 2ead820c73445f38fcec1f73e4f7601164c05ed5 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Mon, 8 Apr 2024 15:57:30 -0700 Subject: [PATCH 02/20] Added unit test for DateTimeOffset in TdsValueSetter to be wrriten in a packet. --- .../DataCommon/DateTimeOffsetList.cs | 22 +++++++ ....Data.SqlClient.ManualTesting.Tests.csproj | 2 + .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 66 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs new file mode 100644 index 0000000000..8d139719b5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs @@ -0,0 +1,22 @@ +// 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 System.Threading.Tasks; +using Microsoft.Data.SqlClient.Server; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.DataCommon +{ + public class DateTimeOffsetList : SqlDataRecord + { + public DateTimeOffsetList(DateTimeOffset dateTimeOffset) + : base(new SqlMetaData("dateTimeOffset", SqlDbType.DateTimeOffset, 0, 1)) + { + this.SetValues(dateTimeOffset); + } + + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 676f8126c6..78a4da87fd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -203,6 +203,7 @@ + @@ -270,6 +271,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs new file mode 100644 index 0000000000..e454f4c16d --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -0,0 +1,66 @@ +// 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 Microsoft.Data.SqlClient.ManualTesting.Tests.DataCommon; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.UdtTest +{ + public class UdtDateTimeOffsetTest + { + private readonly string _connectionString = null; + private readonly string _udtTableType = DataTestUtility.GetUniqueName("DataTimeOffsetTableType"); + + public UdtDateTimeOffsetTest() + { + _connectionString = DataTestUtility.TCPConnectionString; + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] + public void SelectFromSqlParameterShouldSucceed() + { + using (SqlConnection connection = SetupUserDefinedTableType(_connectionString, _udtTableType)) + { + try + { + DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 12, 34, 56, TimeSpan.Zero); + var param = new SqlParameter + { + ParameterName = "@params", + SqlDbType = SqlDbType.Structured, + TypeName = $"dbo.{_udtTableType}", + Value = new DateTimeOffsetList[] { new DateTimeOffsetList(dateTimeOffset) } + }; + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = "SELECT * FROM @params"; + cmd.Parameters.Add(param); + var result = cmd.ExecuteScalar(); + Assert.Equal(dateTimeOffset, result); + } + } + finally + { + DataTestUtility.DropUserDefinedType(connection, _udtTableType); + } + } + } + + private static SqlConnection SetupUserDefinedTableType(string connectionString, string tableTypeName) + { + SqlConnection connection = new(connectionString); + connection.Open(); + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandType = CommandType.Text; + cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([Value] DATETIMEOFFSET(1) NOT NULL) "; + cmd.ExecuteNonQuery(); + } + return connection; + } + } +} From 3f3313920909fdb30bd5aaad430235cced2721d3 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Mon, 8 Apr 2024 16:13:47 -0700 Subject: [PATCH 03/20] Removed unused using and sorted remaining ones. --- .../tests/ManualTests/DataCommon/DateTimeOffsetList.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs index 8d139719b5..41e80c41eb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs @@ -4,9 +4,7 @@ using System; using System.Data; -using System.Threading.Tasks; using Microsoft.Data.SqlClient.Server; -using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.DataCommon { @@ -17,6 +15,5 @@ public DateTimeOffsetList(DateTimeOffset dateTimeOffset) { this.SetValues(dateTimeOffset); } - } } From 2515ef46cb48da3a17beaab9a24a67594277dd27 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Tue, 9 Apr 2024 08:40:39 -0700 Subject: [PATCH 04/20] Apply suggested fix instead. --- .../src/Microsoft/Data/SqlClient/TdsValueSetter.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index 384eb20395..a15e157d34 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -697,10 +697,11 @@ internal void SetDateTimeOffset(DateTimeOffset value) short offset = (short)value.Offset.TotalMinutes; #if NETCOREAPP - Span result = stackalloc byte[9]; + Span result = stackalloc byte[8]; BinaryPrimitives.WriteInt64LittleEndian(result, time); - BinaryPrimitives.WriteInt32LittleEndian(result.Slice(3), days); - _stateObj.WriteByteSpan(result.Slice(0, 6)); + _stateObj.WriteByteSpan(result.Slice(0, length - 5)); + BinaryPrimitives.WriteInt32LittleEndian(result, days); + _stateObj.WriteByteSpan(result.Slice(0, 3)); #else _stateObj.WriteByteArray(BitConverter.GetBytes(time), length - 5, 0); // time _stateObj.WriteByteArray(BitConverter.GetBytes(days), 3, 0); // date From 8ae7ab3b4f61cc180006a06ee4de73136a6131bf Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Tue, 16 Apr 2024 07:08:34 -0700 Subject: [PATCH 05/20] Apply suggestion from code review. --- .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index e454f4c16d..3b7f0cc50e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.UdtTest public class UdtDateTimeOffsetTest { private readonly string _connectionString = null; - private readonly string _udtTableType = DataTestUtility.GetUniqueName("DataTimeOffsetTableType"); + private readonly string _udtTableType = DataTestUtility.GetUniqueNameForSqlServer("DataTimeOffsetTableType"); public UdtDateTimeOffsetTest() { @@ -22,45 +22,43 @@ public UdtDateTimeOffsetTest() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] public void SelectFromSqlParameterShouldSucceed() { - using (SqlConnection connection = SetupUserDefinedTableType(_connectionString, _udtTableType)) + using SqlConnection connection = new(_connectionString); + connection.Open(); + SetupUserDefinedTableType(connection, _udtTableType); + + try { - try + DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 12, 34, 56, TimeSpan.Zero); + var param = new SqlParameter { - DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 12, 34, 56, TimeSpan.Zero); - var param = new SqlParameter - { - ParameterName = "@params", - SqlDbType = SqlDbType.Structured, - TypeName = $"dbo.{_udtTableType}", - Value = new DateTimeOffsetList[] { new DateTimeOffsetList(dateTimeOffset) } - }; + ParameterName = "@params", + SqlDbType = SqlDbType.Structured, + TypeName = $"dbo.{_udtTableType}", + Value = new DateTimeOffsetList[] { new DateTimeOffsetList(dateTimeOffset) } + }; - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = "SELECT * FROM @params"; - cmd.Parameters.Add(param); - var result = cmd.ExecuteScalar(); - Assert.Equal(dateTimeOffset, result); - } - } - finally + using (var cmd = connection.CreateCommand()) { - DataTestUtility.DropUserDefinedType(connection, _udtTableType); + cmd.CommandText = "SELECT * FROM @params"; + cmd.Parameters.Add(param); + var result = cmd.ExecuteScalar(); + Assert.Equal(dateTimeOffset, result); } } + finally + { + DataTestUtility.DropUserDefinedType(connection, _udtTableType); + } } - private static SqlConnection SetupUserDefinedTableType(string connectionString, string tableTypeName) + private static void SetupUserDefinedTableType(SqlConnection connection, string tableTypeName) { - SqlConnection connection = new(connectionString); - connection.Open(); using (SqlCommand cmd = connection.CreateCommand()) { cmd.CommandType = CommandType.Text; cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([Value] DATETIMEOFFSET(1) NOT NULL) "; cmd.ExecuteNonQuery(); } - return connection; } } } From 10b000695addf8b231b6c0947fa2a12028cf420a Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Tue, 16 Apr 2024 07:16:22 -0700 Subject: [PATCH 06/20] Move DateTimeOffsetList inside the unit test. --- .../DataCommon/DateTimeOffsetList.cs | 19 ------------------- ....Data.SqlClient.ManualTesting.Tests.csproj | 1 - .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 11 ++++++++++- 3 files changed, 10 insertions(+), 21 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs deleted file mode 100644 index 41e80c41eb..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DateTimeOffsetList.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 Microsoft.Data.SqlClient.Server; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.DataCommon -{ - public class DateTimeOffsetList : SqlDataRecord - { - public DateTimeOffsetList(DateTimeOffset dateTimeOffset) - : base(new SqlMetaData("dateTimeOffset", SqlDbType.DateTimeOffset, 0, 1)) - { - this.SetValues(dateTimeOffset); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 78a4da87fd..ce2b274b4f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -271,7 +271,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 3b7f0cc50e..e5b7de2ac3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -4,11 +4,20 @@ using System; using System.Data; -using Microsoft.Data.SqlClient.ManualTesting.Tests.DataCommon; +using Microsoft.Data.SqlClient.Server; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.UdtTest { + public class DateTimeOffsetList : SqlDataRecord + { + public DateTimeOffsetList(DateTimeOffset dateTimeOffset) + : base(new SqlMetaData("dateTimeOffset", SqlDbType.DateTimeOffset, 0, 1)) + { + this.SetValues(dateTimeOffset); + } + } + public class UdtDateTimeOffsetTest { private readonly string _connectionString = null; From 7c89a7935aa62f3045df5fda023be3e1a36649dd Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Fri, 26 Apr 2024 13:32:04 -0700 Subject: [PATCH 07/20] Add explanation to why 3 bytes time and 3 bytes are used which is always what they have been before. --- .../Data/SqlClient/TdsValueSetter.cs | 23 ++++++++++++++++++- .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index a15e157d34..bfae864e69 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -697,9 +697,30 @@ internal void SetDateTimeOffset(DateTimeOffset value) short offset = (short)value.Offset.TotalMinutes; #if NETCOREAPP + // In TDS protocol: + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/786f5b8a-f87d-4980-9070-b9b7274c681d + // + // date is represented as one 3 - byte unsigned integer that represents the number of days since January 1, year 1. + // + // time(n) is represented as one unsigned integer that represents the number of 10 - n second increments since 12 AM within a day. + // The length, in bytes, of that integer depends on the scale n as follows: + // 3 bytes if 0 <= n < = 2. + // 4 bytes if 3 <= n < = 4. + // 5 bytes if 5 <= n < = 7. + // For example: + // DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); + // time = 23:59:59 is represented as 863990 in 3 bytes or { 246, 46, 13, 0, 0, 0, 0, 0 } in bytes array + Span result = stackalloc byte[8]; + + // https://learn.microsoft.com/en-us/dotnet/api/system.buffers.binary.binaryprimitives.writeint64bigendian?view=net-8.0 + // WriteInt64LittleEndian requires 8 bytes to write the value. + // However, the maximum time of day value is 863990 which can be represented in 3 bytes only. Thus, the last 5 bytes are not used. BinaryPrimitives.WriteInt64LittleEndian(result, time); - _stateObj.WriteByteSpan(result.Slice(0, length - 5)); + // DateTimeOffset has a length of 8 bytes. So, 8 - 5 = 3 bytes, for time which is what is stored in the result byte array. + _stateObj.WriteByteSpan(result.Slice(0, length - 5)); // this writes the 3 bytes time value to the state object + + // Date is represented as 3 bytes. So, 3 bytes are written to the state object. BinaryPrimitives.WriteInt32LittleEndian(result, days); _stateObj.WriteByteSpan(result.Slice(0, 3)); #else diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index e5b7de2ac3..36a2395426 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -37,7 +37,7 @@ public void SelectFromSqlParameterShouldSucceed() try { - DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 12, 34, 56, TimeSpan.Zero); + DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); var param = new SqlParameter { ParameterName = "@params", From 8ce93423ac054e2209fe78575e5ec55d3e4c1b7b Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Fri, 26 Apr 2024 13:47:45 -0700 Subject: [PATCH 08/20] Updated comment for why 3 bytes is used for time. --- .../src/Microsoft/Data/SqlClient/TdsValueSetter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index bfae864e69..33c72cd0c3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -708,14 +708,14 @@ internal void SetDateTimeOffset(DateTimeOffset value) // 4 bytes if 3 <= n < = 4. // 5 bytes if 5 <= n < = 7. // For example: - // DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); - // time = 23:59:59 is represented as 863990 in 3 bytes or { 246, 46, 13, 0, 0, 0, 0, 0 } in bytes array + // DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); // using scale of 0 + // time = 23:59:59, zero scale, is represented as 863990 in 3 bytes or { 246, 46, 13, 0, 0, 0, 0, 0 } in bytes array Span result = stackalloc byte[8]; // https://learn.microsoft.com/en-us/dotnet/api/system.buffers.binary.binaryprimitives.writeint64bigendian?view=net-8.0 // WriteInt64LittleEndian requires 8 bytes to write the value. - // However, the maximum time of day value is 863990 which can be represented in 3 bytes only. Thus, the last 5 bytes are not used. + // However, the maximum time of day value of 863990 which can be represented in 3 bytes only. Thus, the last 5 bytes are not used. BinaryPrimitives.WriteInt64LittleEndian(result, time); // DateTimeOffset has a length of 8 bytes. So, 8 - 5 = 3 bytes, for time which is what is stored in the result byte array. _stateObj.WriteByteSpan(result.Slice(0, length - 5)); // this writes the 3 bytes time value to the state object From 61e1ae68f7ee4a3d4e68f989213a6365e17be2c0 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Mon, 29 Apr 2024 11:45:17 -0700 Subject: [PATCH 09/20] Added millisecond to the unit test. --- .../src/Microsoft/Data/SqlClient/TdsValueSetter.cs | 2 +- .../tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index 33c72cd0c3..1a36941777 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -709,7 +709,7 @@ internal void SetDateTimeOffset(DateTimeOffset value) // 5 bytes if 5 <= n < = 7. // For example: // DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); // using scale of 0 - // time = 23:59:59, zero scale, is represented as 863990 in 3 bytes or { 246, 46, 13, 0, 0, 0, 0, 0 } in bytes array + // time = 23:59:59, scale is 1, is represented as 863990 in 3 bytes or { 246, 46, 13, 0, 0, 0, 0, 0 } in bytes array Span result = stackalloc byte[8]; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 36a2395426..bf473bc107 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -37,7 +37,7 @@ public void SelectFromSqlParameterShouldSucceed() try { - DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); + DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, 500, TimeSpan.Zero); var param = new SqlParameter { ParameterName = "@params", From 671a9646f57b227e4f3c9c0740a8ea1f81bb9694 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Tue, 30 Apr 2024 14:27:42 -0700 Subject: [PATCH 10/20] Add unit test that tests all scales, 0 to 7, of DateTimeOffset. --- .../Data/SqlClient/TdsValueSetter.cs | 7 +- .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 86 ++++++++++++++++++- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index 1a36941777..d4cb652fa7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -715,10 +715,11 @@ internal void SetDateTimeOffset(DateTimeOffset value) // https://learn.microsoft.com/en-us/dotnet/api/system.buffers.binary.binaryprimitives.writeint64bigendian?view=net-8.0 // WriteInt64LittleEndian requires 8 bytes to write the value. - // However, the maximum time of day value of 863990 which can be represented in 3 bytes only. Thus, the last 5 bytes are not used. BinaryPrimitives.WriteInt64LittleEndian(result, time); - // DateTimeOffset has a length of 8 bytes. So, 8 - 5 = 3 bytes, for time which is what is stored in the result byte array. - _stateObj.WriteByteSpan(result.Slice(0, length - 5)); // this writes the 3 bytes time value to the state object + // The DateTimeOffset length is variable depending on the scale, 1 to 7, used. + // If length = 8, 8 - 5 = 3 bytes is used for time. + // If length = 10, 10 - 5 = 5 bytes is used for time. + _stateObj.WriteByteSpan(result.Slice(0, length - 5)); // this writes the time value to the state object using dynamic length based on the scale. // Date is represented as 3 bytes. So, 3 bytes are written to the state object. BinaryPrimitives.WriteInt32LittleEndian(result, days); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index bf473bc107..f088f6feee 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -12,7 +12,16 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.UdtTest public class DateTimeOffsetList : SqlDataRecord { public DateTimeOffsetList(DateTimeOffset dateTimeOffset) - : base(new SqlMetaData("dateTimeOffset", SqlDbType.DateTimeOffset, 0, 1)) + : base(new SqlMetaData("dateTimeOffset", SqlDbType.DateTimeOffset, 0, 1)) // this is using scale 1 + { + this.SetValues(dateTimeOffset); + } + } + + public class DateTimeOffsetVariableScale : SqlDataRecord + { + public DateTimeOffsetVariableScale(DateTimeOffset dateTimeOffset, int scale) + : base(new SqlMetaData("dateTimeOffset", SqlDbType.DateTimeOffset, 0, (byte)scale)) // this is using variable scale { this.SetValues(dateTimeOffset); } @@ -28,6 +37,7 @@ public UdtDateTimeOffsetTest() _connectionString = DataTestUtility.TCPConnectionString; } + // This unit test is for the reported issue #2423 using a specific scale of 1 [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] public void SelectFromSqlParameterShouldSucceed() { @@ -60,6 +70,58 @@ public void SelectFromSqlParameterShouldSucceed() } } + // This unit test is to ensure that DateTimeOffset with all scales are working as expected + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] + public void DateTimeOffsetAllScalesTestShouldSucceed() + { + string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpType"); + + using SqlConnection connection = new(_connectionString); + connection.Open(); + + try + { + // Use different scale for each test: 0 to 7 + for (int scale = 0; scale <= 7; scale++) + { + DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); + if (scale > 0) + { + // Add 987 milliseconds and some microseconds to the dateTimeOffset + dateTimeOffset = dateTimeOffset.AddMilliseconds(987.654); + } + + DataTestUtility.DropUserDefinedType(connection, tvpTypeName); + SetupDateTimeOffsetTableType(connection,tvpTypeName, scale); + + var param = new SqlParameter + { + ParameterName = "@params", + SqlDbType = SqlDbType.Structured, + Scale = (byte)scale, + TypeName = $"dbo.{tvpTypeName}", + Value = new DateTimeOffsetVariableScale[] { new DateTimeOffsetVariableScale(dateTimeOffset, scale) } + }; + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = "SELECT * FROM @params"; + cmd.Parameters.Add(param); + var result = cmd.ExecuteScalar(); + Assert.Equal(dateTimeOffset, result); + } + } + } + catch(Exception ex) + { + Console.WriteLine(ex.Message); + } + finally + { + DataTestUtility.DropUserDefinedType(connection, tvpTypeName); + } + } + private static void SetupUserDefinedTableType(SqlConnection connection, string tableTypeName) { using (SqlCommand cmd = connection.CreateCommand()) @@ -69,5 +131,27 @@ private static void SetupUserDefinedTableType(SqlConnection connection, string t cmd.ExecuteNonQuery(); } } + + private static void SetupDateTimeOffsetTableType(SqlConnection connection, string tableTypeName, int scale) + { + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandType = CommandType.Text; + cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([Value] DATETIMEOFFSET({scale}) NOT NULL) "; + cmd.ExecuteNonQuery(); + } + } + + private static void xsql(SqlConnection conn, string sql) + { + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = sql; + cmd.ExecuteNonQuery(); + } + + private static void DropType(SqlConnection conn, string typeName) + { + xsql(conn, string.Format("if exists(select 1 from sys.types where name='{0}') begin drop type {1} end", typeName.Substring(1, typeName.Length - 2), typeName)); + } } } From 0aafbd3a2a684a3b7c826c7a3e12c6d32b0f21a3 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Tue, 30 Apr 2024 14:45:19 -0700 Subject: [PATCH 11/20] Remove defined functions that were never used. --- .../ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index f088f6feee..0566a16997 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -141,17 +141,5 @@ private static void SetupDateTimeOffsetTableType(SqlConnection connection, strin cmd.ExecuteNonQuery(); } } - - private static void xsql(SqlConnection conn, string sql) - { - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = sql; - cmd.ExecuteNonQuery(); - } - - private static void DropType(SqlConnection conn, string typeName) - { - xsql(conn, string.Format("if exists(select 1 from sys.types where name='{0}') begin drop type {1} end", typeName.Substring(1, typeName.Length - 2), typeName)); - } } } From ce64aa7dfd9b9b8343fe24aa6ba65f33ee94e20d Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Tue, 30 Apr 2024 14:52:05 -0700 Subject: [PATCH 12/20] Remove catch block. --- .../tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 0566a16997..22d4e56fe8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -112,10 +112,6 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() } } } - catch(Exception ex) - { - Console.WriteLine(ex.Message); - } finally { DataTestUtility.DropUserDefinedType(connection, tvpTypeName); From dc69ac17f8cafe0e32f1a1856135ad0b990a240d Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Tue, 30 Apr 2024 16:48:08 -0700 Subject: [PATCH 13/20] Use decreasing fractional seconds in unit test for datetimeoffset scales. --- .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 22d4e56fe8..d47b672530 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -82,13 +82,25 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() try { // Use different scale for each test: 0 to 7 - for (int scale = 0; scale <= 7; scale++) + int fromScale = 7; + int toScale = 7; + + for (int scale = fromScale; scale <= toScale; scale++) { DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); - if (scale > 0) + if (scale > 0 && scale <= 2) + { + dateTimeOffset = dateTimeOffset.AddMilliseconds(1); + } + else if (scale >= 3 && scale <= 4) + { + // add 1 thousandth of a millisecond + dateTimeOffset = dateTimeOffset.AddMilliseconds(0.001); + } + else if (scale >= 5 && scale <= 7) { - // Add 987 milliseconds and some microseconds to the dateTimeOffset - dateTimeOffset = dateTimeOffset.AddMilliseconds(987.654); + // add 1 ten-thousandth of a millisecond + dateTimeOffset = dateTimeOffset.AddMilliseconds(0.0001); } DataTestUtility.DropUserDefinedType(connection, tvpTypeName); From 5949f6a0592a31aaf05b1cdfd11c48e985719551 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Tue, 30 Apr 2024 17:25:18 -0700 Subject: [PATCH 14/20] Fixed stating and ending scale to be from 0 to 7. --- .../tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index d47b672530..2d9395546d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -82,7 +82,7 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() try { // Use different scale for each test: 0 to 7 - int fromScale = 7; + int fromScale = 0; int toScale = 7; for (int scale = fromScale; scale <= toScale; scale++) From dfa5bdbb1d6d748043d5a85c2f5599250f61aa5f Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Wed, 1 May 2024 08:56:28 -0700 Subject: [PATCH 15/20] Test scales 0 to 2. --- .../tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 2d9395546d..a3e5dbf3da 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -83,7 +83,7 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() { // Use different scale for each test: 0 to 7 int fromScale = 0; - int toScale = 7; + int toScale = 2; for (int scale = fromScale; scale <= toScale; scale++) { From 36eb854a5abdfa6120d84e2efad5fca7658d128f Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Wed, 1 May 2024 11:19:41 -0700 Subject: [PATCH 16/20] Put the scale 0 to 7 back. --- .../Data/SqlClient/TdsValueSetter.cs | 2 +- .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 47 ++++++++++++------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index d4cb652fa7..8598ebdd93 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -702,7 +702,7 @@ internal void SetDateTimeOffset(DateTimeOffset value) // // date is represented as one 3 - byte unsigned integer that represents the number of days since January 1, year 1. // - // time(n) is represented as one unsigned integer that represents the number of 10 - n second increments since 12 AM within a day. + // time(n) is represented as one unsigned integer that represents the number of 10^-n second increments since 12 AM within a day. // The length, in bytes, of that integer depends on the scale n as follows: // 3 bytes if 0 <= n < = 2. // 4 bytes if 3 <= n < = 4. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index a3e5dbf3da..4184b410a5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -7,7 +7,7 @@ using Microsoft.Data.SqlClient.Server; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.UdtTest +namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public class DateTimeOffsetList : SqlDataRecord { @@ -29,6 +29,17 @@ public DateTimeOffsetVariableScale(DateTimeOffset dateTimeOffset, int scale) public class UdtDateTimeOffsetTest { + private static readonly long[] TICKS_FROM_SCALE = { + 10000000, + 1000000, + 100000, + 10000, + 1000, + 100, + 10, + 1, + }; + private readonly string _connectionString = null; private readonly string _udtTableType = DataTestUtility.GetUniqueNameForSqlServer("DataTimeOffsetTableType"); @@ -70,7 +81,7 @@ public void SelectFromSqlParameterShouldSucceed() } } - // This unit test is to ensure that DateTimeOffset with all scales are working as expected + // This unit test is to ensure that time in DateTimeOffset with all scales are working as expected [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] public void DateTimeOffsetAllScalesTestShouldSucceed() { @@ -83,25 +94,13 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() { // Use different scale for each test: 0 to 7 int fromScale = 0; - int toScale = 2; + int toScale = 7; for (int scale = fromScale; scale <= toScale; scale++) { DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); - if (scale > 0 && scale <= 2) - { - dateTimeOffset = dateTimeOffset.AddMilliseconds(1); - } - else if (scale >= 3 && scale <= 4) - { - // add 1 thousandth of a millisecond - dateTimeOffset = dateTimeOffset.AddMilliseconds(0.001); - } - else if (scale >= 5 && scale <= 7) - { - // add 1 ten-thousandth of a millisecond - dateTimeOffset = dateTimeOffset.AddMilliseconds(0.0001); - } + // This additional precision is to compare the time part of the DateTimeOffset with the scale used in the test. + dateTimeOffset = dateTimeOffset.AddSeconds(.123456789012); DataTestUtility.DropUserDefinedType(connection, tvpTypeName); SetupDateTimeOffsetTableType(connection,tvpTypeName, scale); @@ -120,7 +119,19 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() cmd.CommandText = "SELECT * FROM @params"; cmd.Parameters.Add(param); var result = cmd.ExecuteScalar(); - Assert.Equal(dateTimeOffset, result); + + if (dateTimeOffset != (DateTimeOffset)result) + { + Console.WriteLine($"Scale: {scale} dateTimeOffset: {dateTimeOffset} result: {result}"); + } + + // Get the time part of the DateTimeOffset and scale it to the scale used in the test. + long timeScaledInput = dateTimeOffset.TimeOfDay.Ticks / TICKS_FROM_SCALE[scale]; + // Get the time part of the result and scale it to the scale used in the test + long timeScaledOutput = ((DateTimeOffset)result).TimeOfDay.Ticks / TICKS_FROM_SCALE[scale]; + + // Both time parts should be the same. The parameter passed in should be identical to the output regardless of scale used. + Assert.Equal(timeScaledInput, timeScaledOutput); } } } From ce27490464b756dac9a0dec937857443670b8147 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Wed, 1 May 2024 12:11:54 -0700 Subject: [PATCH 17/20] Fix comment 10 - n. It's actually 1o to the power of negative n. --- .../src/Microsoft/Data/SqlClient/TdsValueSetter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index 8598ebdd93..dc797ced8e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -702,7 +702,8 @@ internal void SetDateTimeOffset(DateTimeOffset value) // // date is represented as one 3 - byte unsigned integer that represents the number of days since January 1, year 1. // - // time(n) is represented as one unsigned integer that represents the number of 10^-n second increments since 12 AM within a day. + // time(n) is represented as one unsigned integer that represents the number of 10^-n, + // (10 to the power of negative n), second increments since 12 AM within a day. // The length, in bytes, of that integer depends on the scale n as follows: // 3 bytes if 0 <= n < = 2. // 4 bytes if 3 <= n < = 4. From 699e896ecde59ed83235968d239580e58cd0726c Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Wed, 1 May 2024 12:46:10 -0700 Subject: [PATCH 18/20] Add more comments for explation of expected test result. --- .../tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 4184b410a5..12df02d986 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -130,7 +130,8 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() // Get the time part of the result and scale it to the scale used in the test long timeScaledOutput = ((DateTimeOffset)result).TimeOfDay.Ticks / TICKS_FROM_SCALE[scale]; - // Both time parts should be the same. The parameter passed in should be identical to the output regardless of scale used. + // Both time parts should be the same. The parameter passed in would have been scaled (rounded off). + // So, scaling the input parameter to the same scale used should match the result that was scaled. Assert.Equal(timeScaledInput, timeScaledOutput); } } From 0519f7d40ef7a0012cc5e1bba4f9f81de1f18539 Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Wed, 1 May 2024 14:41:02 -0700 Subject: [PATCH 19/20] Applied suggested unit test changes. --- .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 12df02d986..22fc7c7361 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -99,11 +99,13 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() for (int scale = fromScale; scale <= toScale; scale++) { DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); - // This additional precision is to compare the time part of the DateTimeOffset with the scale used in the test. - dateTimeOffset = dateTimeOffset.AddSeconds(.123456789012); + + // Add sub-second offset corresponding to the scale being tested + TimeSpan subSeconds = TimeSpan.FromTicks((long)(TimeSpan.TicksPerSecond / Math.Pow(10, scale))); + dateTimeOffset = dateTimeOffset.Add(subSeconds); DataTestUtility.DropUserDefinedType(connection, tvpTypeName); - SetupDateTimeOffsetTableType(connection,tvpTypeName, scale); + SetupDateTimeOffsetTableType(connection, tvpTypeName, scale); var param = new SqlParameter { @@ -119,20 +121,7 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() cmd.CommandText = "SELECT * FROM @params"; cmd.Parameters.Add(param); var result = cmd.ExecuteScalar(); - - if (dateTimeOffset != (DateTimeOffset)result) - { - Console.WriteLine($"Scale: {scale} dateTimeOffset: {dateTimeOffset} result: {result}"); - } - - // Get the time part of the DateTimeOffset and scale it to the scale used in the test. - long timeScaledInput = dateTimeOffset.TimeOfDay.Ticks / TICKS_FROM_SCALE[scale]; - // Get the time part of the result and scale it to the scale used in the test - long timeScaledOutput = ((DateTimeOffset)result).TimeOfDay.Ticks / TICKS_FROM_SCALE[scale]; - - // Both time parts should be the same. The parameter passed in would have been scaled (rounded off). - // So, scaling the input parameter to the same scale used should match the result that was scaled. - Assert.Equal(timeScaledInput, timeScaledOutput); + Assert.Equal(dateTimeOffset, result); } } } From 0f8914f39f05ebfdc49fc5551162fddbd1f3b08d Mon Sep 17 00:00:00 2001 From: v-arellegue Date: Wed, 1 May 2024 14:54:59 -0700 Subject: [PATCH 20/20] Removed unused variable. --- .../ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 22fc7c7361..a09d00895c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -29,17 +29,6 @@ public DateTimeOffsetVariableScale(DateTimeOffset dateTimeOffset, int scale) public class UdtDateTimeOffsetTest { - private static readonly long[] TICKS_FROM_SCALE = { - 10000000, - 1000000, - 100000, - 10000, - 1000, - 100, - 10, - 1, - }; - private readonly string _connectionString = null; private readonly string _udtTableType = DataTestUtility.GetUniqueNameForSqlServer("DataTimeOffsetTableType");