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/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 5eb7d7cdeb..1c6ae364e7 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -1573,7 +1573,7 @@ override public Stream GetStream(int i) else { // Grab already read data - data = _data[i].SqlBinary.Value; + data = _data[i].ByteArray; } // If non-sequential then we just have a read-only MemoryStream @@ -2969,7 +2969,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met } else { - byte[] value = data.IsNull ? Array.Empty() : data.SqlBinary.Value; + byte[] value = data.IsNull ? Array.Empty() : data.ByteArray; return (T)(object)new MemoryStream(value, writable: false); } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 7e04f6b9c5..7026302e26 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -5500,11 +5500,11 @@ internal static object GetNullSqlValue(SqlBuffer nullVal, SqlMetaDataPriv md, Sq case SqlDbType.Binary: case SqlDbType.VarBinary: case SqlDbType.Image: - nullVal.SqlBinary = SqlBinary.Null; + nullVal.SetToNullOfType(SqlBuffer.StorageType.SqlBinary); break; case SqlDbType.UniqueIdentifier: - nullVal.SqlGuid = SqlGuid.Null; + nullVal.SetToNullOfType(SqlBuffer.StorageType.SqlGuid); break; case SqlDbType.Bit: @@ -5932,7 +5932,11 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt case TdsEnums.SQLUNIQUEID: { Debug.Assert(length == 16, "invalid length for SqlGuid type!"); +#if NET8_0_OR_GREATER value.SqlGuid = new SqlGuid(unencryptedBytes); // doesn't copy the byte array +#else + value.SqlGuid = SqlTypeWorkarounds.SqlGuidCtor(unencryptedBytes, true); // doesn't copy the byte array +#endif break; } @@ -5953,7 +5957,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt unencryptedBytes = bytes; } - value.SqlBinary = new SqlBinary(unencryptedBytes); // doesn't copy the byte array + value.ByteArray = unencryptedBytes; // doesn't copy the byte array break; } @@ -6146,11 +6150,7 @@ internal bool TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, int length, T } else { -#if NET7_0_OR_GREATER - value.SqlBinary = SqlBinary.WrapBytes(b); -#else - value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array -#endif + value.ByteArray = b; // doesn't copy the byte array } break; @@ -6443,12 +6443,8 @@ internal bool TryReadSqlValueInternal(SqlBuffer value, byte tdsType, int length, { return false; } -#if NET7_0_OR_GREATER - value.SqlBinary = SqlBinary.WrapBytes(b); -#else - value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); -#endif + value.ByteArray = b; // doesn't copy byte array break; } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netcore.cs index 7ce991530a..43ed8bbee4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netcore.cs @@ -122,5 +122,31 @@ private struct SqlBinaryCaster internal SqlBinaryLookalike Fake; } #endregion + + #region Work around inability to access SqlGuid.ctor(byte[], bool) + internal static SqlGuid SqlGuidCtor(byte[] value, bool ignored) + { + // Construct a SqlGuid without allocating/copying the byte[]. This provides + // the same behavior as SqlGuid.ctor(byte[], bool). + var c = default(SqlGuidCaster); + c.Fake._value = value; + return c.Real; + } + + [StructLayout(LayoutKind.Sequential)] + private struct SqlGuidLookalike + { + internal byte[] _value; + } + + [StructLayout(LayoutKind.Explicit)] + private struct SqlGuidCaster + { + [FieldOffset(0)] + internal SqlGuid Real; + [FieldOffset(0)] + internal SqlGuidLookalike Fake; + } + #endregion } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs index e85c446cd1..f0e6e743aa 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -1720,7 +1720,7 @@ virtual public XmlReader GetXmlReader(int i) if (_data[i].IsNull) { // A 'null' stream - return SqlTypeWorkarounds.SqlXmlCreateSqlXmlReader(new MemoryStream(new byte[0], writable: false), closeInput: true, async: false); + return SqlTypeWorkarounds.SqlXmlCreateSqlXmlReader(new MemoryStream(Array.Empty(), writable: false), closeInput: true, async: false); } else { @@ -1765,12 +1765,12 @@ override public Stream GetStream(int i) if (_data[i].IsNull) { // A 'null' stream - data = new byte[0]; + data = Array.Empty(); } else { // Grab already read data - data = _data[i].SqlBinary.Value; + data = _data[i].ByteArray; } // If non-sequential then we just have a read-only MemoryStream @@ -3308,7 +3308,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met } else { - byte[] value = data.IsNull ? Array.Empty() : data.SqlBinary.Value; + byte[] value = data.IsNull ? Array.Empty() : data.ByteArray; return (T)(object)new MemoryStream(value, writable: false); } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 7a9bbfdfd3..8742b52c6f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -6306,11 +6306,11 @@ internal static object GetNullSqlValue( case SqlDbType.Binary: case SqlDbType.VarBinary: case SqlDbType.Image: - nullVal.SqlBinary = SqlBinary.Null; + nullVal.SetToNullOfType(SqlBuffer.StorageType.SqlBinary); break; case SqlDbType.UniqueIdentifier: - nullVal.SqlGuid = SqlGuid.Null; + nullVal.SetToNullOfType(SqlBuffer.StorageType.SqlGuid); break; case SqlDbType.Bit: @@ -6741,7 +6741,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt unencryptedBytes = bytes; } - value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(unencryptedBytes, true); // doesn't copy the byte array + value.ByteArray = unencryptedBytes; // doesn't copy the byte array break; } @@ -6938,7 +6938,7 @@ internal bool TryReadSqlValue(SqlBuffer value, } else { - value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array + value.ByteArray = b; // doesn't copy the byte array } break; @@ -7238,8 +7238,8 @@ internal bool TryReadSqlValueInternal(SqlBuffer value, byte tdsType, int length, { return false; } - value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array + value.ByteArray = b; // doesn't copy the byte array break; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.netfx.cs index 53bcedf146..b2d3082fd9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.netfx.cs @@ -68,7 +68,7 @@ internal static Stream GetStream(SmiEventSink_Default sink, ITypedGettersV3 gett if (isDbNull) { // "null" stream - data = new byte[0]; + data = Array.Empty(); } else { @@ -147,15 +147,6 @@ private static void GetNullOutputParameterSmi(SmiMetaData metaData, SqlBuffer ta { result = DBNull.Value; } - else if (SqlBuffer.StorageType.SqlBinary == stype) - { - // special case SqlBinary, 'cause tds parser never sets SqlBuffer to null, just to empty! - targetBuffer.SqlBinary = SqlBinary.Null; - } - else if (SqlBuffer.StorageType.SqlGuid == stype) - { - targetBuffer.SqlGuid = SqlGuid.Null; - } else { targetBuffer.SetToNullOfType(stype); @@ -190,7 +181,7 @@ SqlBuffer targetBuffer // destination case SqlDbType.Image: case SqlDbType.Timestamp: case SqlDbType.VarBinary: - targetBuffer.SqlBinary = GetSqlBinary_Unchecked(sink, getters, ordinal); + targetBuffer.ByteArray = GetByteArray_Unchecked(sink, getters, ordinal); break; case SqlDbType.Bit: targetBuffer.Boolean = GetBoolean_Unchecked(sink, getters, ordinal); 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..65a876cd41 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs @@ -109,7 +109,7 @@ internal struct Storage private bool _isNull; private StorageType _type; private Storage _value; - private object _object; // String, SqlBinary, SqlCachedBuffer, SqlGuid, SqlString, SqlXml + private object _object; // String, byte[] for SqlBinary, SqlCachedBuffer, SqlString, SqlXml internal SqlBuffer() { @@ -179,7 +179,19 @@ internal byte[] ByteArray get { ThrowIfNull(); - return SqlBinary.Value; + + if (StorageType.SqlBinary == _type) + { + return (byte[])_object; + } + return ((SqlBinary)SqlValue).Value; // anything else we haven't thought of goes through boxing. + } + set + { + Debug.Assert(IsEmpty, "setting value a second time?"); + _object = value; + _type = StorageType.SqlBinary; + _isNull = value is null; } } @@ -653,22 +665,16 @@ internal SqlBinary SqlBinary { get { - if (StorageType.SqlBinary == _type) + if (IsNull) { - if (IsNull) - { - return SqlBinary.Null; - } - return (SqlBinary)_object; + return SqlBinary.Null; } - return (SqlBinary)SqlValue; // anything else we haven't thought of goes through boxing. - } - set - { - Debug.Assert(IsEmpty, "setting value a second time?"); - _object = value; - _type = StorageType.SqlBinary; - _isNull = value.IsNull; + +#if NET7_0_OR_GREATER + return SqlBinary.WrapBytes(ByteArray); +#else + return SqlTypeWorkarounds.SqlBinaryCtor(ByteArray, true); // doesn't copy the byte array +#endif } } @@ -815,14 +821,17 @@ internal SqlGuid SqlGuid } else if (StorageType.SqlGuid == _type) { - return IsNull ? SqlGuid.Null : (SqlGuid)_object; + return IsNull ? SqlGuid.Null : new SqlGuid(_value._guid); } return (SqlGuid)SqlValue; // anything else we haven't thought of goes through boxing. } set { Debug.Assert(IsEmpty, "setting value a second time?"); - _object = value; + if (!value.IsNull) + { + _value._guid = value.Value; + } _type = StorageType.SqlGuid; _isNull = value.IsNull; }