From f5df519f5e9783d6a726fb370678e05a04a3084b Mon Sep 17 00:00:00 2001 From: Evgeny Akhtimirov Date: Tue, 9 Apr 2024 15:04:53 -0400 Subject: [PATCH] Fix | SqlBuffer.SqlGuid (#2310) --- .gitignore | 3 + .../src/Microsoft/Data/SqlClient/SqlBuffer.cs | 2 +- .../Microsoft.Data.SqlClient.Tests.csproj | 1 + .../tests/FunctionalTests/SqlBufferTests.cs | 253 ++++++++++++++++++ 4 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs diff --git a/.gitignore b/.gitignore index daeaf68c7a..bb6f58f9bd 100644 --- a/.gitignore +++ b/.gitignore @@ -353,6 +353,9 @@ healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ +# JetBrains Rider (cross platform .NET IDE) working folder +.idea/ + # Ionide (cross platform F# VS Code tools) working folder .ionide/ diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs index 9cfc077da4..31678a3107 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs @@ -811,7 +811,7 @@ internal SqlGuid SqlGuid { if (StorageType.Guid == _type) { - return new SqlGuid(_value._guid); + return IsNull ? SqlGuid.Null : new SqlGuid(_value._guid); } else if (StorageType.SqlGuid == _type) { diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index d6b8a8565c..8cd1972ded 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -33,6 +33,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs new file mode 100644 index 0000000000..6aef4b60b2 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs @@ -0,0 +1,253 @@ +// 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.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace Microsoft.Data.SqlClient.Tests +{ + public sealed class SqlBufferTests + { + static SqlBufferTests() + { + const string sqlBufferTypeFullName = "Microsoft.Data.SqlClient.SqlBuffer"; + const string storageTypeName = nameof(SqlBufferProxy.StorageType); + + var assembly = typeof(SqlClientFactory).Assembly; + _sqlBufferType = assembly.GetType(sqlBufferTypeFullName) + ?? throw new Exception($"Type not found [{sqlBufferTypeFullName}]"); + _storageTypeType = _sqlBufferType.GetNestedTypes(BindingFlags.NonPublic) + .FirstOrDefault(x => x.Name == storageTypeName) + ?? throw new Exception($"Type not found [{sqlBufferTypeFullName}+{storageTypeName}]"); + } + + private static readonly Type _sqlBufferType; + private static readonly Type _storageTypeType; + private readonly SqlBufferProxy _target = new(); + + public static IEnumerable GetStorageTypeValues() + { +#if NET6_0_OR_GREATER + return Enum.GetValues() + .Select(x => new object[] { x }); +#else + return Enum.GetValues(typeof(SqlBufferProxy.StorageType)) + .OfType() + .Select(x => new object[] { x }); +#endif + } + + [Theory] + [MemberData(nameof(GetStorageTypeValues))] + public void StorageTypeInProxyShouldHaveTheSameValueAsOriginal(SqlBufferProxy.StorageType expected) + { + var originalEnumName = Enum.GetName(_storageTypeType, (int)expected); + + Assert.Equal(expected.ToString(), originalEnumName); + } + + [Fact] + public void GuidShouldThrowWhenSqlGuidNullIsSet() + { + _target.SqlGuid = SqlGuid.Null; + + Assert.Throws(() => _target.Guid); + } + + [Theory] + [InlineData(SqlBufferProxy.StorageType.Guid)] + [InlineData(SqlBufferProxy.StorageType.SqlGuid)] + public void GuidShouldThrowWhenSetToNullOfTypeIsCalled(SqlBufferProxy.StorageType storageType) + { + _target.SetToNullOfType(storageType); + + Assert.Throws(() => _target.Guid); + } + + [Fact] + public void GuidShouldReturnWhenGuidIsSet() + { + var expected = Guid.NewGuid(); + _target.Guid = expected; + + Assert.Equal(expected, _target.Guid); + } + + [Fact] + public void GuidShouldReturnExpectedWhenSqlGuidIsSet() + { + var expected = Guid.NewGuid(); + _target.SqlGuid = expected; + + Assert.Equal(expected, _target.Guid); + } + + [Theory] + [InlineData(SqlBufferProxy.StorageType.Guid)] + [InlineData(SqlBufferProxy.StorageType.SqlGuid)] + public void SqlGuidShouldReturnSqlNullWhenSetToNullOfTypeIsCalled(SqlBufferProxy.StorageType storageType) + { + _target.SetToNullOfType(storageType); + + Assert.Equal(SqlGuid.Null, _target.SqlGuid); + } + + [Fact] + public void SqlGuidShouldReturnSqlGuidNullWhenSqlGuidNullIsSet() + { + _target.SqlGuid = SqlGuid.Null; + + Assert.Equal(SqlGuid.Null, _target.SqlGuid); + } + + [Fact] + public void SqlGuidShouldReturnExpectedWhenGuidIsSet() + { + var guid = Guid.NewGuid(); + SqlGuid expected = guid; + _target.Guid = guid; + + Assert.Equal(expected, _target.SqlGuid); + } + + [Fact] + public void SqlGuidShouldReturnExpectedWhenSqlGuidIsSet() + { + SqlGuid expected = Guid.NewGuid(); + _target.SqlGuid = expected; + + Assert.Equal(expected, _target.SqlGuid); + } + + [Fact] + public void SqlValueShouldReturnExpectedWhenGuidIsSet() + { + var guid = Guid.NewGuid(); + SqlGuid expected = guid; + _target.Guid = guid; + + Assert.Equal(expected, _target.SqlValue); + } + + [Fact] + public void SqlValueShouldReturnExpectedWhenSqlGuidIsSet() + { + SqlGuid expected = Guid.NewGuid(); + _target.SqlGuid = expected; + + Assert.Equal(expected, _target.SqlValue); + } + + public sealed class SqlBufferProxy + { + public enum StorageType + { + Empty = 0, + Boolean, + Byte, + DateTime, + Decimal, + Double, + Int16, + Int32, + Int64, + Guid, + Money, + Single, + String, + SqlBinary, + SqlCachedBuffer, + SqlGuid, + SqlXml, + Date, + DateTime2, + DateTimeOffset, + Time, + } + + private static readonly PropertyInfo _guidProperty; + private static readonly PropertyInfo _sqlGuidProperty; + private static readonly PropertyInfo _sqlValueProperty; + private static readonly MethodInfo _setToNullOfTypeMethod; + private readonly object _instance; + + static SqlBufferProxy() + { + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + _guidProperty = _sqlBufferType.GetProperty(nameof(Guid), flags); + _sqlGuidProperty = _sqlBufferType.GetProperty(nameof(SqlGuid), flags); + _sqlValueProperty = _sqlBufferType.GetProperty(nameof(SqlValue), flags); + _setToNullOfTypeMethod = _sqlBufferType.GetMethod(nameof(SetToNullOfType), flags); + } + + public SqlBufferProxy() + { + _instance = Activator.CreateInstance(_sqlBufferType, true); + } + + public Guid Guid + { + get => GetPropertyValue(_guidProperty); + set => SetPropertyValue(_guidProperty, value); + } + + public SqlGuid SqlGuid + { + get => GetPropertyValue(_sqlGuidProperty); + set => SetPropertyValue(_sqlGuidProperty, value); + } + + public object SqlValue + { + get => GetPropertyValue(_sqlValueProperty); + } + + public void SetToNullOfType(StorageType storageType) + { +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + _setToNullOfTypeMethod + .Invoke(_instance, BindingFlags.DoNotWrapExceptions, null, new object[] { (int)storageType }, null); +#else + _setToNullOfTypeMethod.Invoke(_instance, new object[] { (int)storageType }); +#endif + } + + private T GetPropertyValue(PropertyInfo property) + { +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return (T)property.GetValue(_instance, BindingFlags.DoNotWrapExceptions, null, null, null); +#else + try + { + return (T)property.GetValue(_instance); + } + catch (TargetInvocationException e) + { + throw e.InnerException!; + } +#endif + } + + private void SetPropertyValue(PropertyInfo property, object value) + { +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + property.SetValue(_instance, value, BindingFlags.DoNotWrapExceptions, null, null, null); +#else + try + { + property.SetValue(_instance, value); + } + catch (TargetInvocationException e) + { + throw e.InnerException!; + } +#endif + } + } + } +}