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 e02711fe8d..2141d65533 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
@@ -5861,7 +5861,7 @@ internal static object GetNullSqlValue(SqlBuffer nullVal,
case SqlDbTypeExtensions.Vector:
nullVal.SetToNullOfType(SqlBuffer.StorageType.Vector);
- nullVal.SetVectorInfo(MetaType.GetVectorElementCount(md.length, md.scale), md.scale, true);
+ nullVal.SetToVectorInfo(MetaType.GetVectorElementCount(md.length, md.scale), md.scale, true);
break;
default:
@@ -6488,7 +6488,7 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
// object from binary payload.
int elementCount = BinaryPrimitives.ReadUInt16LittleEndian(b.AsSpan(2));
byte elementType = b[4];
- value.SetVectorInfo(elementCount, elementType, false);
+ value.SetToVectorInfo(elementCount, elementType, false);
break;
case TdsEnums.SQLCHAR:
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 0715bd8205..3d40b4a5b6 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
@@ -6058,7 +6058,7 @@ internal static object GetNullSqlValue(SqlBuffer nullVal,
case SqlDbTypeExtensions.Vector:
nullVal.SetToNullOfType(SqlBuffer.StorageType.Vector);
- nullVal.SetVectorInfo(MetaType.GetVectorElementCount(md.length, md.scale), md.scale, true);
+ nullVal.SetToVectorInfo(MetaType.GetVectorElementCount(md.length, md.scale), md.scale, true);
break;
default:
@@ -6684,7 +6684,7 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
// object from binary payload.
int elementCount = BinaryPrimitives.ReadUInt16LittleEndian(b.AsSpan(2));
byte elementType = b[4];
- value.SetVectorInfo(elementCount, elementType, false);
+ value.SetToVectorInfo(elementCount, elementType, false);
break;
case TdsEnums.SQLCHAR:
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ISqlVector.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ISqlVector.cs
index ca9fe35743..cbc8bf099e 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ISqlVector.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ISqlVector.cs
@@ -31,5 +31,10 @@ internal interface ISqlVector
/// Returns the total size in bytes for sending SqlVector value on TDS.
///
int Size { get; }
+
+ ///
+ /// Returns a JSON serialized string representation of the current instance.
+ ///
+ string GetString();
}
}
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 39d2758d62..49f95413df 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Buffers.Binary;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.Globalization;
@@ -13,6 +14,51 @@ namespace Microsoft.Data.SqlClient
{
internal sealed class SqlBuffer
{
+ #region Constants
+ // These variables store pre-boxed bool values to be used when returning a boolean as an
+ // object. If these are not used a new value is boxed each time one is needed which leads
+ // to a lot of garbage which needs to be collected
+ private static readonly object True = true;
+ private static readonly object False = false;
+
+ // These formats work with DateTime stricts
+ private static readonly string[] Sql2008DateTime2Formats = new[] {
+ "yyyy-MM-dd HH:mm:ss",
+ "yyyy-MM-dd HH:mm:ss.f",
+ "yyyy-MM-dd HH:mm:ss.ff",
+ "yyyy-MM-dd HH:mm:ss.fff",
+ "yyyy-MM-dd HH:mm:ss.ffff",
+ "yyyy-MM-dd HH:mm:ss.fffff",
+ "yyyy-MM-dd HH:mm:ss.ffffff",
+ "yyyy-MM-dd HH:mm:ss.fffffff",
+ };
+
+ // These formats work with DateTimeOffset structs
+ private static readonly string[] Sql2008DateTimeOffsetFormats = new[] {
+ "yyyy-MM-dd HH:mm:ss zzz",
+ "yyyy-MM-dd HH:mm:ss.f zzz",
+ "yyyy-MM-dd HH:mm:ss.ff zzz",
+ "yyyy-MM-dd HH:mm:ss.fff zzz",
+ "yyyy-MM-dd HH:mm:ss.ffff zzz",
+ "yyyy-MM-dd HH:mm:ss.fffff zzz",
+ "yyyy-MM-dd HH:mm:ss.ffffff zzz",
+ "yyyy-MM-dd HH:mm:ss.fffffff zzz",
+ };
+
+ // These formats only work with TimeSpan structs
+ private static readonly string[] Sql2008TimeFormats = new[] {
+ @"hh\:mm\:ss",
+ @"hh\:mm\:ss\.f",
+ @"hh\:mm\:ss\.ff",
+ @"hh\:mm\:ss\.fff",
+ @"hh\:mm\:ss\.ffff",
+ @"hh\:mm\:ss\.fffff",
+ @"hh\:mm\:ss\.ffffff",
+ @"hh\:mm\:ss\.fffffff",
+ };
+
+ #endregion
+
internal enum StorageType
{
Empty = 0,
@@ -40,466 +86,428 @@ internal enum StorageType
Vector,
}
- internal struct DateTimeInfo
- {
- // This is used to store DateTime
- internal int _daypart;
- internal int _timepart;
- }
-
- internal struct NumericInfo
- {
- // This is used to store Decimal data
- internal int _data1;
- internal int _data2;
- internal int _data3;
- internal int _data4;
- internal byte _precision;
- internal byte _scale;
- internal bool _positive;
- }
-
- internal struct TimeInfo
- {
- internal long _ticks;
- internal byte _scale;
- }
-
- internal struct DateTime2Info
- {
- internal int _date;
- internal TimeInfo _timeInfo;
- }
-
- internal struct DateTimeOffsetInfo
- {
- internal DateTime2Info _dateTime2Info;
- internal short _offset;
- }
-
- internal struct VectorInfo
- {
- internal int _elementCount;
- internal byte _elementType;
- }
-
- [StructLayout(LayoutKind.Explicit)]
- internal struct Storage
- {
- [FieldOffset(0)]
- internal bool _boolean;
- [FieldOffset(0)]
- internal byte _byte;
- [FieldOffset(0)]
- internal DateTimeInfo _dateTimeInfo;
- [FieldOffset(0)]
- internal double _double;
- [FieldOffset(0)]
- internal NumericInfo _numericInfo;
- [FieldOffset(0)]
- internal short _int16;
- [FieldOffset(0)]
- internal int _int32;
- [FieldOffset(0)]
- internal long _int64; // also used to store Money, UtcDateTime, Date , and Time
- [FieldOffset(0)]
- internal Guid _guid;
- [FieldOffset(0)]
- internal float _single;
- [FieldOffset(0)]
- internal TimeInfo _timeInfo;
- [FieldOffset(0)]
- internal DateTime2Info _dateTime2Info;
- [FieldOffset(0)]
- internal DateTimeOffsetInfo _dateTimeOffsetInfo;
- [FieldOffset(0)]
- internal VectorInfo _vectorInfo;
- }
+ #region Member Variables
- private bool _isNull;
+ // Storage for reference types, eg, String, SqlBinary, SqlCachedBuffer, SqlGuid.
+ private object _object;
+
private StorageType _type;
+
+ // Storage for value types, eg, bool, int32, datetime2. Note that on construction of the
+ // SqlBuffer object, this will be initialized to an empty Storage instance.
private Storage _value;
- private object _object; // String, SqlBinary, SqlCachedBuffer, SqlGuid, SqlString, SqlXml
+ #endregion
+
+ #region Constructors
+
internal SqlBuffer()
{
}
private SqlBuffer(SqlBuffer value)
- { // Clone
+ {
// value types
- _isNull = value._isNull;
+ IsNull = value.IsNull;
_type = value._type;
+
// ref types - should also be read only unless at some point we allow this data
// to be mutable, then we will need to copy
_value = value._value;
_object = value._object;
}
- internal bool IsEmpty => _type == StorageType.Empty;
+ #endregion
+
+ #region General Properties
- internal bool IsNull => _isNull;
+ internal Type ClrType
+ {
+ get => _type switch
+ {
+ StorageType.Boolean => typeof(bool),
+ StorageType.Byte => typeof(byte),
+ StorageType.DateTime => typeof(DateTime),
+ StorageType.DateTime2 => typeof(DateTime),
+ StorageType.DateTimeOffset => typeof(DateTimeOffset),
+ StorageType.Decimal => typeof(decimal),
+ StorageType.Double => typeof(double),
+ StorageType.Empty => null,
+ StorageType.Guid => typeof(Guid),
+ StorageType.Int16 => typeof(short),
+ StorageType.Int32 => typeof(int),
+ StorageType.Int64 => typeof(long),
+ StorageType.Json => typeof(string),
+ StorageType.Money => typeof(double),
+ StorageType.Single => typeof(float),
+ StorageType.SqlBinary => typeof(byte[]),
+ StorageType.SqlCachedBuffer => typeof(string),
+ StorageType.SqlGuid => typeof(Guid),
+ StorageType.SqlXml => typeof(string),
+ StorageType.String => typeof(string),
+ StorageType.Vector => typeof(byte[]),
+
+ // @TODO: IF this property is meant to return the type that you'll get back when
+ // calling Value, then this isn't correct.
+ #if NET
+ StorageType.Time => typeof(TimeSpan),
+ #endif
+
+ _ => null
+ };
+ }
+
+ internal bool IsEmpty => _type == StorageType.Empty;
- internal StorageType VariantInternalStorageType => _type;
+ internal bool IsNull { get; private set; }
- internal Storage GetVectorInfo()
+ internal Type SqlType
{
- if (_type == StorageType.Vector)
+ get => _type switch
{
- return _value;
- }
- throw new InvalidOperationException();
- }
+ StorageType.Boolean => typeof(SqlBoolean),
+ StorageType.Byte => typeof(SqlByte),
+ StorageType.DateTime => typeof(SqlDateTime),
+ StorageType.Decimal => typeof(SqlDecimal),
+ StorageType.Double => typeof(SqlDouble),
+ StorageType.Empty => null,
+ StorageType.Guid => typeof(SqlGuid),
+ StorageType.Int16 => typeof(SqlInt16),
+ StorageType.Int32 => typeof(SqlInt32),
+ StorageType.Int64 => typeof(SqlInt64),
+ StorageType.Json => typeof(SqlJson),
+ StorageType.Money => typeof(SqlMoney),
+ StorageType.Single => typeof(SqlSingle),
+ StorageType.SqlBinary => typeof(object),
+ StorageType.SqlCachedBuffer => typeof(SqlString),
+ StorageType.SqlGuid => typeof(SqlGuid),
+ StorageType.SqlXml => typeof(SqlXml),
+ StorageType.String => typeof(SqlString),
+ StorageType.Vector => typeof(object),
+ _ => null
+ };
+ }
+
+ internal StorageType VariantInternalStorageType => _type;
+ #endregion
+
+ #region Type Conversion Properties
+
internal bool Boolean
{
- get
- {
- ThrowIfNull();
-
- if (StorageType.Boolean == _type)
- {
- return _value._boolean;
- }
- return (bool)Value; // anything else we haven't thought of goes through boxing.
- }
- set
- {
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._boolean = value;
- _type = StorageType.Boolean;
- _isNull = false;
- }
+ get => GetValue(StorageType.Boolean, _value._boolean);
+ set => SetValue(StorageType.Boolean, ref _value._boolean, value);
}
-
+
internal byte Byte
{
- get
- {
- ThrowIfNull();
-
- if (StorageType.Byte == _type)
- {
- return _value._byte;
- }
- return (byte)Value; // anything else we haven't thought of goes through boxing.
- }
- set
- {
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._byte = value;
- _type = StorageType.Byte;
- _isNull = false;
- }
+ get => GetValue(StorageType.Byte, _value._byte);
+ set => SetValue(StorageType.Byte, ref _value._byte, value);
}
internal byte[] ByteArray
{
get
{
- if (_type != StorageType.Vector)
+ if (_type is not StorageType.Vector)
{
+ // Must be checked here because SqlBinary allows null.
ThrowIfNull();
}
+
return SqlBinary.Value;
}
}
-
- internal DateTime DateTime
+
+ #if NET
+ internal DateOnly DateOnly
{
get
{
ThrowIfNull();
-
- if (StorageType.Date == _type)
- {
- return DateTime.MinValue.AddDays(_value._int32);
- }
- if (StorageType.DateTime2 == _type)
- {
- return new DateTime(GetTicksFromDateTime2Info(_value._dateTime2Info));
- }
- if (StorageType.DateTime == _type)
- {
- return SqlTypeWorkarounds.SqlDateTimeToDateTime(_value._dateTimeInfo._daypart, _value._dateTimeInfo._timepart);
- }
- return (DateTime)Value; // anything else we haven't thought of goes through boxing.
+ return _type == StorageType.Date
+ ? DateOnly.MinValue.AddDays(_value._int32)
+ : (DateOnly)Value;
}
}
-
- #region Decimal
- internal decimal Decimal
+ #endif
+
+ internal DateTime DateTime
{
get
{
ThrowIfNull();
-
- if (StorageType.Decimal == _type)
- {
- if (_value._numericInfo._data4 != 0 || _value._numericInfo._scale > 28)
- {
- // Only removing trailing zeros from a decimal part won't hit its value!
- if (_value._numericInfo._scale > 0)
- {
- int zeroCnt = FindTrailingZerosAndPrec((uint)_value._numericInfo._data1, (uint)_value._numericInfo._data2,
- (uint)_value._numericInfo._data3, (uint)_value._numericInfo._data4,
- _value._numericInfo._scale, out int precision);
-
- int minScale = _value._numericInfo._scale - zeroCnt; // minimum possible sacle after removing the trailing zeros.
-
- if (zeroCnt > 0 && minScale <= 28 && precision <= 29)
- {
- SqlDecimal sqlValue = new(_value._numericInfo._precision, _value._numericInfo._scale, _value._numericInfo._positive,
- _value._numericInfo._data1, _value._numericInfo._data2,
- _value._numericInfo._data3, _value._numericInfo._data4);
-
- int integral = precision - minScale;
- int newPrec = 29;
-
- if (integral != 1 && precision != 29)
- {
- newPrec = 28;
- }
-
- try
- {
- // Precision could be 28 or 29
- // ex: (precision == 29 && scale == 28)
- // valid: (+/-)7.1234567890123456789012345678
- // invalid: (+/-)8.1234567890123456789012345678
- return SqlDecimal.ConvertToPrecScale(sqlValue, newPrec, newPrec - integral).Value;
- }
- catch (OverflowException)
- {
- throw new OverflowException(SQLResource.ConversionOverflowMessage);
- }
- }
- }
- throw new OverflowException(SQLResource.ConversionOverflowMessage);
- }
- return new decimal(_value._numericInfo._data1, _value._numericInfo._data2, _value._numericInfo._data3, !_value._numericInfo._positive, _value._numericInfo._scale);
- }
- if (StorageType.Money == _type)
- {
- long l = _value._int64;
- bool isNegative = false;
- if (l < 0)
- {
- isNegative = true;
- l = -l;
- }
- return new decimal((int)(l & 0xffffffff), (int)(l >> 32), 0, isNegative, 4);
- }
- return (decimal)Value; // anything else we haven't thought of goes through boxing.
- }
- }
-
- ///
- /// Returns number of trailing zeros using the supplied parameters.
- ///
- /// An 32-bit unsigned integer which will be combined with data2, data3, and data4
- /// An 32-bit unsigned integer which will be combined with data1, data3, and data4
- /// An 32-bit unsigned integer which will be combined with data1, data2, and data4
- /// An 32-bit unsigned integer which will be combined with data1, data2, and data3
- /// The number of decimal places
- /// OUT |The number of digits without trailing zeros
- /// Number of trailing zeros
- private static int FindTrailingZerosAndPrec(uint data1, uint data2, uint data3, uint data4, byte scale, out int valuablePrecision)
- {
- // Make local copy of data to avoid modifying input.
- Span rgulNumeric = stackalloc uint[4] { data1, data2, data3, data4 };
- int zeroCnt = 0; //Number of trailing zero digits
- int precCnt = 0; //Valuable precision
- uint uiRem = 0; //Remainder of a division by 10
- int len = 4; // Max possible items
-
- //Retrieve each digit from the lowest significant digit
- while (len > 1 || rgulNumeric[0] != 0)
- {
- SqlDecimalDivBy(rgulNumeric, ref len, 10, out uiRem);
- if (uiRem == 0 && precCnt == 0)
- {
- zeroCnt++;
- }
- else
+ return _type switch
{
- precCnt++;
- }
- }
-
- if (uiRem == 0)
- {
- zeroCnt = scale;
+ StorageType.Date => DateTime.MinValue.AddDays(_value._int32),
+ StorageType.DateTime => _value._dateTimeInfo.ToDateTime(),
+ StorageType.DateTime2 => _value._dateTime2Info.ToDateTime(),
+ _ => (DateTime)Value,
+ };
}
-
- // if scale of the number has not been reached, pad remaining number with zeros.
- if (zeroCnt + precCnt <= scale)
- {
- precCnt = scale - zeroCnt + 1;
- }
- valuablePrecision = precCnt;
- return zeroCnt;
}
- ///
- /// Multi-precision one super-digit divide in place.
- /// U = U / D,
- /// R = U % D
- /// (Length of U can decrease)
- ///
- /// InOut | U
- /// InOut | Number of items with non-zero value in U between 1 to 4
- /// In | D
- /// Out | R
- private static void SqlDecimalDivBy(Span data, ref int len, uint divisor, out uint remainder)
- {
- uint uiCarry = 0;
- ulong ulAccum;
- ulong ulDivisor = (ulong)divisor;
- int iLen = len;
-
- while (iLen > 0)
+ internal DateTimeOffset DateTimeOffset
+ {
+ get
{
- iLen--;
- ulAccum = (((ulong)uiCarry) << 32) + (ulong)(data[iLen]);
- data[iLen] = (uint)(ulAccum / ulDivisor);
- uiCarry = (uint)(ulAccum - (ulong)data[iLen] * ulDivisor); // (ULONG) (ulAccum % divisor)
+ ThrowIfNull();
+ return _type == StorageType.DateTimeOffset
+ ? _value._dateTimeOffsetInfo.ToDateTimeOffset()
+ : (DateTimeOffset)Value;
}
- remainder = uiCarry;
-
- // Normalize multi-precision number - remove leading zeroes
- while (len > 1 && data[len - 1] == 0)
- { len--; }
}
- #endregion
- internal double Double
+ internal decimal Decimal
{
get
{
ThrowIfNull();
-
- if (StorageType.Double == _type)
+ return _type switch
{
- return _value._double;
- }
- return (double)Value; // anything else we haven't thought of goes through boxing.
- }
- set
- {
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._double = value;
- _type = StorageType.Double;
- _isNull = false;
+ StorageType.Decimal => _value._numericInfo.ToDecimal(),
+ StorageType.Money => GetSqlMoneyFromLong(_value._int64).ToDecimal(),
+ _ => (decimal)Value,
+ };
}
}
-
+
+ internal double Double
+ {
+ get => GetValue(StorageType.Double, _value._double);
+ set => SetValue(StorageType.Double, ref _value._double, value);
+ }
+
internal Guid Guid
{
get
{
ThrowIfNull();
- if (StorageType.Guid == _type)
- {
- return _value._guid;
- }
- else if (StorageType.SqlGuid == _type)
+ return _type switch
{
- return ((SqlGuid)_object).Value;
- }
- return (Guid)Value;
- }
- set
- {
- Debug.Assert(IsEmpty, "setting value a second time?");
- _type = StorageType.Guid;
- _value._guid = value;
- _isNull = false;
+ StorageType.Guid => _value._guid,
+ StorageType.SqlGuid => ((SqlGuid)_object).Value,
+ _ => (Guid)Value,
+ };
}
+ set => SetValue(StorageType.Guid, ref _value._guid, value);
}
-
+
internal short Int16
+ {
+ get => GetValue(StorageType.Int16, _value._int16);
+ set => SetValue(StorageType.Int16, ref _value._int16, value);
+ }
+
+ internal int Int32
+ {
+ get => GetValue(StorageType.Int32, _value._int32);
+ set => SetValue(StorageType.Int32, ref _value._int32, value);
+ }
+
+ internal long Int64
+ {
+ get => GetValue(StorageType.Int64, _value._int64);
+ set => SetValue(StorageType.Int64, ref _value._int64, value);
+ }
+
+ internal float Single
+ {
+ get => GetValue(StorageType.Single, _value._single);
+ set => SetValue(StorageType.Single, ref _value._single, value);
+ }
+
+ internal SqlString Sql2008DateTimeSqlString
+ {
+ get => _type is StorageType.Date
+ or StorageType.DateTime2
+ or StorageType.DateTimeOffset
+ or StorageType.Time
+ ? IsNull ? SqlString.Null : new SqlString(Sql2008DateTimeString)
+ : (SqlString)SqlValue;
+ }
+
+ internal string Sql2008DateTimeString
{
get
{
ThrowIfNull();
- if (StorageType.Int16 == _type)
+ string formatString;
+ switch (_type)
{
- return _value._int16;
+ case StorageType.Date:
+ formatString = "yyyy-MM-dd";
+ return DateTime.ToString(formatString, DateTimeFormatInfo.InvariantInfo);
+ case StorageType.DateTime2:
+ formatString = Sql2008DateTime2Formats[_value._dateTime2Info._timeInfo._scale];
+ return DateTime.ToString(formatString, DateTimeFormatInfo.InvariantInfo);
+ case StorageType.DateTimeOffset:
+ formatString = Sql2008DateTimeOffsetFormats[_value._dateTimeOffsetInfo._dateTime2Info._timeInfo._scale];
+ return DateTimeOffset.ToString(formatString, DateTimeFormatInfo.InvariantInfo);
+ case StorageType.Time:
+ formatString = Sql2008TimeFormats[_value._timeInfo._scale];
+ return Time.ToString(formatString, DateTimeFormatInfo.InvariantInfo);
+ default:
+ return (string)Value;
}
- return (short)Value; // anything else we haven't thought of goes through boxing.
- }
- set
- {
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._int16 = value;
- _type = StorageType.Int16;
- _isNull = false;
}
}
+
+ internal SqlBinary SqlBinary
+ {
+ get => _type is StorageType.SqlBinary or StorageType.Vector
+ ? IsNull ? SqlBinary.Null : (SqlBinary)_object
+ : (SqlBinary)SqlValue;
+ set => SetObject(StorageType.SqlBinary, value);
+ }
- internal int Int32
+ internal SqlBoolean SqlBoolean
{
- get
- {
- ThrowIfNull();
+ get => _type == StorageType.Boolean
+ ? IsNull ? SqlBoolean.Null : new SqlBoolean(_value._boolean)
+ : (SqlBoolean)SqlValue;
+ }
- if (StorageType.Int32 == _type)
- {
- return _value._int32;
- }
- return (int)Value; // anything else we haven't thought of goes through boxing.
- }
- set
- {
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._int32 = value;
- _type = StorageType.Int32;
- _isNull = false;
- }
+ internal SqlByte SqlByte
+ {
+ get => _type == StorageType.Byte
+ ? IsNull ? SqlByte.Null : new SqlByte(_value._byte)
+ : (SqlByte)SqlValue;
}
- internal long Int64
+ internal SqlCachedBuffer SqlCachedBuffer
{
- get
- {
- ThrowIfNull();
+ get => _type == StorageType.SqlCachedBuffer
+ ? IsNull ? SqlCachedBuffer.Null : (SqlCachedBuffer)_object
+ : (SqlCachedBuffer)SqlValue;
+ set => SetObject(StorageType.SqlCachedBuffer, value);
+ }
+
+ internal SqlDateTime SqlDateTime
+ {
+ get => _type == StorageType.DateTime
+ ? IsNull ? SqlDateTime.Null : _value._dateTimeInfo.ToSqlDateTime()
+ : (SqlDateTime)SqlValue;
+ }
- if (StorageType.Int64 == _type)
- {
- return _value._int64;
- }
- return (long)Value; // anything else we haven't thought of goes through boxing.
- }
- set
+ internal SqlDecimal SqlDecimal
+ {
+ get => _type == StorageType.Decimal
+ ? IsNull ? SqlDecimal.Null : _value._numericInfo.ToSqlDecimal()
+ : (SqlDecimal)SqlValue;
+ }
+
+ internal SqlDouble SqlDouble
+ {
+ get => _type == StorageType.Double
+ ? IsNull ? SqlDouble.Null : new SqlDouble(_value._double)
+ : (SqlDouble)SqlValue;
+ }
+
+ internal SqlGuid SqlGuid
+ {
+ get => _type switch
{
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._int64 = value;
- _type = StorageType.Int64;
- _isNull = false;
- }
+ StorageType.Guid => IsNull ? SqlGuid.Null : new SqlGuid(_value._guid),
+ StorageType.SqlGuid => IsNull ? SqlGuid.Null : (SqlGuid)_object,
+ _ => (SqlGuid)SqlValue
+ };
+ set => SetObject(StorageType.SqlGuid, value);
+ }
+
+ internal SqlInt16 SqlInt16
+ {
+ get => _type == StorageType.Int16
+ ? IsNull ? SqlInt16.Null : new SqlInt16(_value._int16)
+ : (SqlInt16)SqlValue;
}
- internal float Single
+ internal SqlInt32 SqlInt32
{
- get
+ get => _type == StorageType.Int32
+ ? IsNull ? SqlInt32.Null : new SqlInt32(_value._int32)
+ : (SqlInt32)SqlValue;
+ }
+
+ internal SqlInt64 SqlInt64
+ {
+ get => _type == StorageType.Int64
+ ? IsNull ? SqlInt64.Null : new SqlInt64(_value._int64)
+ : (SqlInt64)SqlValue;
+ }
+
+ internal SqlJson SqlJson
+ {
+ get => StorageType.Json == _type
+ ? IsNull ? SqlJson.Null : new SqlJson((string)_object)
+ : (SqlJson)SqlValue;
+ }
+
+ internal SqlMoney SqlMoney
+ {
+ get => _type == StorageType.Money
+ ? IsNull ? SqlMoney.Null : GetSqlMoneyFromLong(_value._int64)
+ : (SqlMoney)SqlValue;
+ }
+
+ internal SqlSingle SqlSingle
+ {
+ get => _type == StorageType.Single
+ ? IsNull ? SqlSingle.Null : new SqlSingle(_value._single)
+ : (SqlSingle)SqlValue;
+ }
+
+ internal SqlString SqlString
+ {
+ get => _type switch
{
- ThrowIfNull();
+ StorageType.Json => IsNull ? SqlString.Null : new SqlString((string)_object),
+ StorageType.String => IsNull ? SqlString.Null : new SqlString((string)_object),
+ StorageType.SqlCachedBuffer => IsNull ? SqlString.Null : ((SqlCachedBuffer)_object).ToSqlString(),
+ StorageType.Vector => IsNull ? SqlString.Null : new SqlString(GetSqlVector().GetString()),
+ _ => (SqlString)SqlValue
+ };
+ }
- if (StorageType.Single == _type)
- {
- return _value._single;
- }
- return (float)Value; // anything else we haven't thought of goes through boxing.
- }
- set
+ internal object SqlValue
+ {
+ get => _type switch
{
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._single = value;
- _type = StorageType.Single;
- _isNull = false;
- }
+ StorageType.Boolean => SqlBoolean,
+ StorageType.Byte => SqlByte,
+ StorageType.Date => IsNull ? DBNull.Value : DateTime,
+ StorageType.DateTime => SqlDateTime,
+ StorageType.DateTime2 => IsNull ? DBNull.Value : DateTime,
+ StorageType.DateTimeOffset => IsNull ? DBNull.Value : DateTimeOffset,
+ StorageType.Decimal => SqlDecimal,
+ StorageType.Double => SqlDouble,
+ StorageType.Empty => DBNull.Value,
+ StorageType.Guid => SqlGuid,
+ StorageType.Int16 => SqlInt16,
+ StorageType.Int32 => SqlInt32,
+ StorageType.Int64 => SqlInt64,
+ StorageType.Json => SqlJson,
+ StorageType.Money => SqlMoney,
+ StorageType.Single => SqlSingle,
+ StorageType.SqlBinary => _object,
+ StorageType.SqlCachedBuffer => IsNull ? SqlXml.Null : ((SqlCachedBuffer)_object).ToSqlXml(),
+ StorageType.SqlGuid => _object,
+ StorageType.SqlXml => IsNull ? SqlXml.Null : (SqlXml)_object,
+ StorageType.String => SqlString,
+ StorageType.Time => IsNull ? DBNull.Value : Time,
+ StorageType.Vector => GetSqlVector(),
+ _ => null
+ };
+ }
+
+ internal SqlXml SqlXml
+ {
+ get => _type == StorageType.SqlXml
+ ? IsNull ? SqlXml.Null : (SqlXml)_object
+ : (SqlXml)SqlValue;
+ set => SetObject(StorageType.SqlXml, value);
}
internal string String
@@ -507,771 +515,102 @@ internal string String
get
{
ThrowIfNull();
- if (_type == StorageType.Vector)
+ return _type switch
{
- var elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType;
- switch (elementType)
- {
- case MetaType.SqlVectorElementType.Float32:
- return GetSqlVector().GetString();
- default:
- throw SQL.VectorTypeNotSupported(elementType.ToString());
- }
- }
- if (StorageType.String == _type || StorageType.Json == _type)
- {
- return (string)_object;
- }
- else if (StorageType.SqlCachedBuffer == _type)
- {
- return ((SqlCachedBuffer)(_object)).ToString();
- }
- return (string)Value; // anything else we haven't thought of goes through boxing.
- }
- }
-
- // use static list of format strings indexed by scale for perf
- private static readonly string[] s_sql2008DateTimeOffsetFormatByScale = new string[] {
- "yyyy-MM-dd HH:mm:ss zzz",
- "yyyy-MM-dd HH:mm:ss.f zzz",
- "yyyy-MM-dd HH:mm:ss.ff zzz",
- "yyyy-MM-dd HH:mm:ss.fff zzz",
- "yyyy-MM-dd HH:mm:ss.ffff zzz",
- "yyyy-MM-dd HH:mm:ss.fffff zzz",
- "yyyy-MM-dd HH:mm:ss.ffffff zzz",
- "yyyy-MM-dd HH:mm:ss.fffffff zzz",
- };
-
- private static readonly string[] s_sql2008DateTime2FormatByScale = new string[] {
- "yyyy-MM-dd HH:mm:ss",
- "yyyy-MM-dd HH:mm:ss.f",
- "yyyy-MM-dd HH:mm:ss.ff",
- "yyyy-MM-dd HH:mm:ss.fff",
- "yyyy-MM-dd HH:mm:ss.ffff",
- "yyyy-MM-dd HH:mm:ss.fffff",
- "yyyy-MM-dd HH:mm:ss.ffffff",
- "yyyy-MM-dd HH:mm:ss.fffffff",
- };
-
- private static readonly string[] s_sql2008TimeFormatByScale = new string[] {
- "HH:mm:ss",
- "HH:mm:ss.f",
- "HH:mm:ss.ff",
- "HH:mm:ss.fff",
- "HH:mm:ss.ffff",
- "HH:mm:ss.fffff",
- "HH:mm:ss.ffffff",
- "HH:mm:ss.fffffff",
- };
-
- internal string Sql2008DateTimeString
- {
- get
- {
- ThrowIfNull();
-
- if (StorageType.Date == _type)
- {
- return DateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo);
- }
- if (StorageType.Time == _type)
- {
- byte scale = _value._timeInfo._scale;
- return new DateTime(_value._timeInfo._ticks).ToString(s_sql2008TimeFormatByScale[scale], DateTimeFormatInfo.InvariantInfo);
- }
- if (StorageType.DateTime2 == _type)
- {
- byte scale = _value._dateTime2Info._timeInfo._scale;
- return DateTime.ToString(s_sql2008DateTime2FormatByScale[scale], DateTimeFormatInfo.InvariantInfo);
- }
- if (StorageType.DateTimeOffset == _type)
- {
- DateTimeOffset dto = DateTimeOffset;
- byte scale = _value._dateTimeOffsetInfo._dateTime2Info._timeInfo._scale;
- return dto.ToString(s_sql2008DateTimeOffsetFormatByScale[scale], DateTimeFormatInfo.InvariantInfo);
- }
- return (string)Value; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlString Sql2008DateTimeSqlString
- {
- get
- {
- if (StorageType.Date == _type ||
- StorageType.Time == _type ||
- StorageType.DateTime2 == _type ||
- StorageType.DateTimeOffset == _type)
- {
- if (IsNull)
- {
- return SqlString.Null;
- }
- return new SqlString(Sql2008DateTimeString);
- }
- return (SqlString)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal TimeSpan Time
- {
- get
- {
- ThrowIfNull();
-
- if (StorageType.Time == _type)
- {
- return new TimeSpan(_value._timeInfo._ticks);
- }
-
- return (TimeSpan)Value; // anything else we haven't thought of goes through boxing.
- }
- }
-
-#if NET
- internal TimeOnly TimeOnly
- {
- get
- {
- ThrowIfNull();
-
- if (StorageType.Time == _type)
- {
- return new TimeOnly(_value._timeInfo._ticks);
- }
-
- return (TimeOnly)Value; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal DateOnly DateOnly
- {
- get
- {
- ThrowIfNull();
-
- if (StorageType.Date == _type)
- {
- return DateOnly.MinValue.AddDays(_value._int32);
- }
- return (DateOnly)Value; // anything else we haven't thought of goes through boxing.
- }
- }
-#endif
-
- internal DateTimeOffset DateTimeOffset
- {
- get
- {
- ThrowIfNull();
-
- if (StorageType.DateTimeOffset == _type)
- {
- TimeSpan offset = new TimeSpan(0, _value._dateTimeOffsetInfo._offset, 0);
- // datetime part presents time in UTC
- return new DateTimeOffset(GetTicksFromDateTime2Info(_value._dateTimeOffsetInfo._dateTime2Info) + offset.Ticks, offset);
- }
-
- return (DateTimeOffset)Value; // anything else we haven't thought of goes through boxing.
- }
- }
-
- private static long GetTicksFromDateTime2Info(DateTime2Info dateTime2Info)
- {
- return (dateTime2Info._date * TimeSpan.TicksPerDay + dateTime2Info._timeInfo._ticks);
- }
-
- internal SqlBinary SqlBinary
- {
- get
- {
- if (_type is StorageType.SqlBinary or StorageType.Vector)
- {
- if (IsNull)
- {
- return SqlBinary.Null;
- }
- return (SqlBinary)_object;
- }
- 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;
- }
- }
-
- internal SqlBoolean SqlBoolean
- {
- get
- {
- if (StorageType.Boolean == _type)
- {
- if (IsNull)
- {
- return SqlBoolean.Null;
- }
- return new SqlBoolean(_value._boolean);
- }
- return (SqlBoolean)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlByte SqlByte
- {
- get
- {
- if (StorageType.Byte == _type)
- {
- if (IsNull)
- {
- return SqlByte.Null;
- }
- return new SqlByte(_value._byte);
- }
- return (SqlByte)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlCachedBuffer SqlCachedBuffer
- {
- get
- {
- if (StorageType.SqlCachedBuffer == _type)
- {
- if (IsNull)
- {
- return SqlCachedBuffer.Null;
- }
- return (SqlCachedBuffer)_object;
- }
- return (SqlCachedBuffer)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- set
- {
- Debug.Assert(IsEmpty, "setting value a second time?");
- _object = value;
- _type = StorageType.SqlCachedBuffer;
- _isNull = value.IsNull;
- }
- }
-
- internal SqlXml SqlXml
- {
- get
- {
- if (StorageType.SqlXml == _type)
- {
- if (IsNull)
- {
- return SqlXml.Null;
- }
- return (SqlXml)_object;
- }
- return (SqlXml)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- set
- {
- Debug.Assert(IsEmpty, "setting value a second time?");
- _object = value;
- _type = StorageType.SqlXml;
- _isNull = value.IsNull;
- }
- }
-
- internal SqlDateTime SqlDateTime
- {
- get
- {
- if (StorageType.DateTime == _type)
- {
- if (IsNull)
- {
- return SqlDateTime.Null;
- }
- return new SqlDateTime(_value._dateTimeInfo._daypart, _value._dateTimeInfo._timepart);
- }
- return (SqlDateTime)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlDecimal SqlDecimal
- {
- get
- {
- if (StorageType.Decimal == _type)
- {
- if (IsNull)
- {
- return SqlDecimal.Null;
- }
- return new SqlDecimal(_value._numericInfo._precision,
- _value._numericInfo._scale,
- _value._numericInfo._positive,
- _value._numericInfo._data1,
- _value._numericInfo._data2,
- _value._numericInfo._data3,
- _value._numericInfo._data4
- );
- }
- return (SqlDecimal)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlDouble SqlDouble
- {
- get
- {
- if (StorageType.Double == _type)
- {
- if (IsNull)
- {
- return SqlDouble.Null;
- }
- return new SqlDouble(_value._double);
- }
- return (SqlDouble)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlGuid SqlGuid
- {
- get
- {
- if (StorageType.Guid == _type)
- {
- return IsNull ? SqlGuid.Null : new SqlGuid(_value._guid);
- }
- else if (StorageType.SqlGuid == _type)
- {
- return IsNull ? SqlGuid.Null : (SqlGuid)_object;
- }
- return (SqlGuid)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- set
- {
- Debug.Assert(IsEmpty, "setting value a second time?");
- _object = value;
- _type = StorageType.SqlGuid;
- _isNull = value.IsNull;
- }
- }
-
- internal SqlInt16 SqlInt16
- {
- get
- {
- if (StorageType.Int16 == _type)
- {
- if (IsNull)
- {
- return SqlInt16.Null;
- }
- return new SqlInt16(_value._int16);
- }
- return (SqlInt16)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlInt32 SqlInt32
- {
- get
- {
- if (StorageType.Int32 == _type)
- {
- if (IsNull)
- {
- return SqlInt32.Null;
- }
- return new SqlInt32(_value._int32);
- }
- return (SqlInt32)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlInt64 SqlInt64
- {
- get
- {
- if (StorageType.Int64 == _type)
- {
- if (IsNull)
- {
- return SqlInt64.Null;
- }
- return new SqlInt64(_value._int64);
- }
- return (SqlInt64)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlMoney SqlMoney
- {
- get
- {
- if (StorageType.Money == _type)
- {
- if (IsNull)
- {
- return SqlMoney.Null;
- }
-#if NET
- return SqlMoney.FromTdsValue(_value._int64);
-#else
- return SqlTypeWorkarounds.SqlMoneyCtor(_value._int64, 1/*ignored*/);
-#endif
- }
- return (SqlMoney)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlSingle SqlSingle
- {
- get
- {
- if (StorageType.Single == _type)
- {
- if (IsNull)
- {
- return SqlSingle.Null;
- }
- return new SqlSingle(_value._single);
- }
- return (SqlSingle)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlString SqlString
- {
- get
- {
- if (_type is StorageType.Vector)
- {
- if (IsNull)
- {
- return SqlString.Null;
- }
- var elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType;
- switch (elementType)
- {
- case MetaType.SqlVectorElementType.Float32:
- return new SqlString(GetSqlVector().GetString());
- default:
- throw SQL.VectorTypeNotSupported(elementType.ToString());
- }
- }
- // String and Json storage type are both strings.
- if (_type is StorageType.String or StorageType.Json)
- {
- if (IsNull)
- {
- return SqlString.Null;
- }
- return new SqlString((string)_object);
- }
- else if (StorageType.SqlCachedBuffer == _type)
- {
- SqlCachedBuffer data = (SqlCachedBuffer)(_object);
- if (data.IsNull)
- {
- return SqlString.Null;
- }
- return data.ToSqlString();
- }
- return (SqlString)SqlValue; // anything else we haven't thought of goes through boxing.
- }
- }
-
- internal SqlJson SqlJson => (StorageType.Json == _type) ? (IsNull ? SqlTypes.SqlJson.Null : new SqlJson((string)_object)) : (SqlJson)SqlValue;
-
- internal SqlVector GetSqlVector() where T : unmanaged
- {
- if (_type is StorageType.Vector)
- {
- if (IsNull)
- {
- return new SqlVector(_value._vectorInfo._elementCount);
- }
- return new SqlVector(SqlBinary.Value);
- }
- return (SqlVector)SqlValue;
- }
-
- internal object SqlValue
- {
- get
- {
- switch (_type)
- {
- case StorageType.Empty:
- return DBNull.Value;
- case StorageType.Boolean:
- return SqlBoolean;
- case StorageType.Byte:
- return SqlByte;
- case StorageType.DateTime:
- return SqlDateTime;
- case StorageType.Decimal:
- return SqlDecimal;
- case StorageType.Double:
- return SqlDouble;
- case StorageType.Int16:
- return SqlInt16;
- case StorageType.Int32:
- return SqlInt32;
- case StorageType.Int64:
- return SqlInt64;
- case StorageType.Guid:
- return SqlGuid;
- case StorageType.Money:
- return SqlMoney;
- case StorageType.Single:
- return SqlSingle;
- case StorageType.String:
- return SqlString;
- case StorageType.Json:
- return SqlJson;
- case StorageType.Vector:
- var elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType;
- switch (elementType)
- {
- case MetaType.SqlVectorElementType.Float32:
- return GetSqlVector();
- default:
- throw SQL.VectorTypeNotSupported(elementType.ToString());
- }
- case StorageType.SqlCachedBuffer:
- {
- SqlCachedBuffer data = (SqlCachedBuffer)(_object);
- if (data.IsNull)
- {
- return SqlXml.Null;
- }
- return data.ToSqlXml();
- }
-
- case StorageType.SqlBinary:
- case StorageType.SqlGuid:
- return _object;
-
- case StorageType.SqlXml:
- if (_isNull)
- {
- return SqlXml.Null;
- }
- Debug.Assert(_object != null);
- return (SqlXml)_object;
-
- case StorageType.Date:
- case StorageType.DateTime2:
- if (_isNull)
- {
- return DBNull.Value;
- }
- return DateTime;
-
- case StorageType.DateTimeOffset:
- if (_isNull)
- {
- return DBNull.Value;
- }
- return DateTimeOffset;
-
- case StorageType.Time:
- if (_isNull)
- {
- return DBNull.Value;
- }
- return Time;
- }
- return null; // need to return the value as an object of some SQL type
+ StorageType.Json => (string)_object,
+ StorageType.String => (string)_object,
+ StorageType.SqlCachedBuffer => ((SqlCachedBuffer)_object).ToString(),
+ StorageType.Vector => GetSqlVector().GetString(),
+ _ => (string)Value
+ };
}
}
-
- // these variables store pre-boxed bool values to be used when returning a boolean
- // in a object typed location, if these are not used a new value is boxed each time
- // one is needed which leads to a lot of garbage which needs to be collected
- private static readonly object s_cachedTrueObject = true;
- private static readonly object s_cachedFalseObject = false;
-
- internal object Value
+ internal TimeSpan Time
{
get
{
- if (IsNull)
- {
- return DBNull.Value;
- }
- switch (_type)
- {
- case StorageType.Empty:
- return DBNull.Value;
- case StorageType.Boolean:
- return Boolean ? s_cachedTrueObject : s_cachedFalseObject; // return pre-boxed values for perf
- case StorageType.Byte:
- return Byte;
- case StorageType.DateTime:
- return DateTime;
- case StorageType.Decimal:
- return Decimal;
- case StorageType.Double:
- return Double;
- case StorageType.Int16:
- return Int16;
- case StorageType.Int32:
- return Int32;
- case StorageType.Int64:
- return Int64;
- case StorageType.Guid:
- return Guid;
- case StorageType.Money:
- return Decimal;
- case StorageType.Single:
- return Single;
- case StorageType.String:
- return String;
- case StorageType.SqlBinary:
- case StorageType.Vector:
- return ByteArray;
- case StorageType.SqlCachedBuffer:
- {
- // If we have a CachedBuffer, it's because it's an XMLTYPE column
- // and we have to return a string when they're asking for the CLS
- // value of the column.
- return ((SqlCachedBuffer)(_object)).ToString();
- }
- case StorageType.SqlGuid:
- return Guid;
- case StorageType.SqlXml:
- {
- // XMLTYPE columns must be returned as string when asking for the CLS value
- SqlXml data = (SqlXml)_object;
- string s = data.Value;
- return s;
- }
- case StorageType.Date:
- return DateTime;
- case StorageType.DateTime2:
- return DateTime;
- case StorageType.DateTimeOffset:
- return DateTimeOffset;
- case StorageType.Time:
- return Time;
- case StorageType.Json:
- return String;
- }
- return null; // need to return the value as an object of some CLS type
+ ThrowIfNull();
+ return _type == StorageType.Time
+ ? new TimeSpan(_value._timeInfo._ticks)
+ : (TimeSpan)Value;
}
}
-
- internal Type GetTypeFromStorageType(bool isSqlType)
+
+ #if NET
+ internal TimeOnly TimeOnly
{
- if (isSqlType)
+ get
{
- switch (_type)
- {
- case StorageType.Empty:
- return null;
- case StorageType.Boolean:
- return typeof(SqlBoolean);
- case StorageType.Byte:
- return typeof(SqlByte);
- case StorageType.DateTime:
- return typeof(SqlDateTime);
- case StorageType.Decimal:
- return typeof(SqlDecimal);
- case StorageType.Double:
- return typeof(SqlDouble);
- case StorageType.Int16:
- return typeof(SqlInt16);
- case StorageType.Int32:
- return typeof(SqlInt32);
- case StorageType.Int64:
- return typeof(SqlInt64);
- case StorageType.Guid:
- return typeof(SqlGuid);
- case StorageType.Money:
- return typeof(SqlMoney);
- case StorageType.Single:
- return typeof(SqlSingle);
- case StorageType.String:
- return typeof(SqlString);
- case StorageType.SqlCachedBuffer:
- return typeof(SqlString);
- case StorageType.SqlBinary:
- case StorageType.Vector:
- return typeof(object);
- case StorageType.SqlGuid:
- return typeof(SqlGuid);
- case StorageType.SqlXml:
- return typeof(SqlXml);
- case StorageType.Json:
- return typeof(SqlJson);
- // Time Date DateTime2 and DateTimeOffset have no direct Sql type to contain them
- }
+ ThrowIfNull();
+ return _type == StorageType.Time
+ ? new TimeOnly(_value._timeInfo._ticks)
+ : (TimeOnly)Value;
}
- else
- { //Is CLR Type
- switch (_type)
+ }
+ #endif
+
+ internal object Value
+ {
+ get => IsNull
+ ? DBNull.Value
+ : _type switch
{
- case StorageType.Empty:
- return null;
- case StorageType.Boolean:
- return typeof(bool);
- case StorageType.Byte:
- return typeof(byte);
- case StorageType.DateTime:
- return typeof(DateTime);
- case StorageType.Decimal:
- return typeof(decimal);
- case StorageType.Double:
- return typeof(double);
- case StorageType.Int16:
- return typeof(short);
- case StorageType.Int32:
- return typeof(int);
- case StorageType.Int64:
- return typeof(long);
- case StorageType.Guid:
- return typeof(Guid);
- case StorageType.Money:
- return typeof(decimal);
- case StorageType.Single:
- return typeof(float);
- case StorageType.String:
- return typeof(string);
- case StorageType.SqlBinary:
- return typeof(byte[]);
- case StorageType.SqlCachedBuffer:
- return typeof(string);
- case StorageType.SqlGuid:
- return typeof(Guid);
- case StorageType.SqlXml:
- return typeof(string);
- case StorageType.Date:
- return typeof(DateTime);
- case StorageType.DateTime2:
- return typeof(DateTime);
- case StorageType.DateTimeOffset:
- return typeof(DateTimeOffset);
- case StorageType.Json:
- return typeof(string);
- case StorageType.Vector:
- return typeof(byte[]);
-#if NET
- case StorageType.Time:
- return typeof(TimeOnly);
-#endif
+ StorageType.Boolean => Boolean ? True : False, // Return pre-boxed values for perf
+ StorageType.Byte => Byte,
+ StorageType.Date => DateTime,
+ StorageType.DateTime => DateTime,
+ StorageType.DateTime2 => DateTime,
+ StorageType.DateTimeOffset => DateTimeOffset,
+ StorageType.Decimal => Decimal,
+ StorageType.Double => Double,
+ StorageType.Empty => DBNull.Value,
+ StorageType.Int16 => Int16,
+ StorageType.Int32 => Int32,
+ StorageType.Int64 => Int64,
+ StorageType.Json => String,
+ StorageType.Guid => Guid,
+ StorageType.Money => Decimal,
+ StorageType.Single => Single,
+ StorageType.SqlBinary => ByteArray,
+ StorageType.SqlGuid => Guid,
+ StorageType.String => String,
+ StorageType.Time => Time,
+ StorageType.Vector => ByteArray,
+
+ // @TODO: Verify that these follow the same pattern as other types
+ // (ie, ClrType => (cast)Value)
+ // If we have a cached buffer, it's because it's an XMLTYPE column and we have
+ // to return a string when they're asking for the CLR value of the column.
+ StorageType.SqlCachedBuffer => ((SqlCachedBuffer)_object).ToString(),
+ StorageType.SqlXml => ((SqlXml)_object).Value,
+
+ _ => null
+ };
+ }
+
+ #endregion
+
+ #region General Methods
+
+ internal void Clear()
+ {
+ IsNull = false;
+ _type = StorageType.Empty;
+ _object = null;
+ }
+
+ internal static void Clear(SqlBuffer[] values)
+ {
+ if (values != null)
+ {
+ for (int i = 0; i < values.Length; ++i)
+ {
+ values[i].Clear();
}
}
-
- return null; // need to return the value as an object of some CLS type
}
-
+
internal static SqlBuffer[] CreateBufferArray(int length)
{
SqlBuffer[] buffers = new SqlBuffer[length];
@@ -1292,263 +631,683 @@ internal static SqlBuffer[] CloneBufferArray(SqlBuffer[] values)
return copy;
}
- internal static void Clear(SqlBuffer[] values)
+ #endregion
+
+ #region Get Methods
+
+ internal T BooleanAs() =>
+ GetValueAs(_value._boolean);
+
+ internal T ByteAs() =>
+ GetValueAs(_value._byte);
+
+ internal T DoubleAs() =>
+ GetValueAs(_value._double);
+
+ internal T Int16As() =>
+ GetValueAs(_value._int16);
+
+ internal T Int32As() =>
+ GetValueAs(_value._int32);
+
+ internal T Int64As() =>
+ GetValueAs(_value._int64);
+
+ internal T SingleAs() =>
+ GetValueAs(_value._single);
+
+ internal SqlVector GetSqlVector()
+ where T : unmanaged =>
+ _type is StorageType.Vector
+ ? IsNull ? _value._vectorInfo.ToNull() : new SqlVector((byte[])_object)
+ : (SqlVector)SqlValue;
+
+ #endregion
+
+ #region Set Methods
+
+ #if NETFRAMEWORK
+ internal void SetToDate(DateTime date) =>
+ SetValue(StorageType.Date, ref _value._int32, date.Subtract(DateTime.MinValue).Days);
+ #endif
+
+ internal void SetToDate(ReadOnlySpan bytes)
{
- if (values != null)
- {
- for (int i = 0; i < values.Length; ++i)
- {
- values[i].Clear();
- }
- }
+ // NOTE: Reordered to optimize JIT generated bounds checks to a single instance,
+ // review generated asm before changing.
+ // @TODO: Verify that ^^^ is still accurate/needed
+ byte thirdByte = bytes[2]; //
+ int dateValue = bytes[0] + (bytes[1] << 8) + (thirdByte << 16);
+ SetValue(StorageType.Date, ref _value._int32, dateValue);
}
-
- internal void Clear()
+
+ internal void SetToDateTime(int dayPart, int timePart)
{
- _isNull = false;
- _type = StorageType.Empty;
- _object = null;
+ SetTypeAndIsNull(StorageType.DateTime, false);
+ _value._dateTimeInfo.FromDateTimeData(dayPart, timePart);
}
-
+
#if NETFRAMEWORK
- internal void SetToDate(DateTime date)
+ internal void SetToDateTime2(DateTime dateTime, byte scale)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
-
- _type = StorageType.Date;
- _value._int32 = date.Subtract(DateTime.MinValue).Days;
- _isNull = false;
+ SetTypeAndIsNull(StorageType.DateTime2, false);
+ _value._dateTime2Info.FromDateTimeAndScale(dateTime, scale);
}
-#endif
-
- internal void SetVectorInfo(int elementCount, byte elementType, bool isNull)
+ #endif
+
+ internal void SetToDateTime2(ReadOnlySpan bytes, byte scale, byte denormalizedScale)
{
- _value._vectorInfo._elementCount = elementCount;
- _value._vectorInfo._elementType = elementType;
- _type = StorageType.Vector;
- _isNull = isNull;
+ SetTypeAndIsNull(StorageType.DateTime2, false);
+ _value._dateTime2Info.FromByteArray(bytes, scale, denormalizedScale);
}
-
- internal void SetToDateTime(int daypart, int timepart)
+
+ internal void SetToDateTimeOffset(DateTimeOffset dateTimeOffset, byte scale)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._dateTimeInfo._daypart = daypart;
- _value._dateTimeInfo._timepart = timepart;
- _type = StorageType.DateTime;
- _isNull = false;
+ SetTypeAndIsNull(StorageType.DateTimeOffset, false);
+ _value._dateTimeOffsetInfo.FromDateTimeOffsetAndScale(dateTimeOffset, scale);
}
-
-#if NETFRAMEWORK
- internal void SetToDateTime2(DateTime dateTime, byte scale)
+
+ internal void SetToDateTimeOffset(ReadOnlySpan bytes, byte scale, byte denormalizedScale)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
-
- _type = StorageType.DateTime2;
- _value._dateTime2Info._timeInfo._ticks = dateTime.TimeOfDay.Ticks;
- _value._dateTime2Info._timeInfo._scale = scale;
- _value._dateTime2Info._date = dateTime.Subtract(DateTime.MinValue).Days;
- _isNull = false;
+ SetTypeAndIsNull(StorageType.DateTimeOffset, false);
+ _value._dateTimeOffsetInfo.FromByteArray(bytes, scale, denormalizedScale);
}
-#endif
internal void SetToDecimal(byte precision, byte scale, bool positive, int[] bits)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._numericInfo._precision = precision;
- _value._numericInfo._scale = scale;
- _value._numericInfo._positive = positive;
- _value._numericInfo._data1 = bits[0];
- _value._numericInfo._data2 = bits[1];
- _value._numericInfo._data3 = bits[2];
- _value._numericInfo._data4 = bits[3];
- _type = StorageType.Decimal;
- _isNull = false;
+ SetTypeAndIsNull(StorageType.Decimal, false);
+ _value._numericInfo.FromDecimalData(precision, scale, positive, bits);
}
- internal void SetToMoney(long value)
+ internal void SetToJson(string value)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
- _value._int64 = value;
- _type = StorageType.Money;
- _isNull = false;
+ SetTypeAndIsNull(StorageType.Json, false);
+ _object = value;
}
+
+ internal void SetToMoney(long value) =>
+ SetValue(StorageType.Money, ref _value._int64, value);
internal void SetToNullOfType(StorageType storageType)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
- _type = storageType;
- _isNull = true;
+ SetTypeAndIsNull(storageType, true);
_object = null;
}
internal void SetToString(string value)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
+ SetTypeAndIsNull(StorageType.String, false);
_object = value;
- _type = StorageType.String;
- _isNull = false;
}
- internal void SetToJson(string value)
+ internal void SetToTime(ReadOnlySpan bytes, byte scale, byte denormalizedScale)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
- _object = value;
- _type = StorageType.Json;
- _isNull = false;
+ SetTypeAndIsNull(StorageType.Time, false);
+ _value._timeInfo.FromByteArray(bytes, scale, denormalizedScale);
}
- internal void SetToDate(ReadOnlySpan bytes)
+ internal void SetToTime(TimeSpan timeSpan, byte scale)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
-
- _type = StorageType.Date;
- _value._int32 = GetDateFromByteArray(bytes);
- _isNull = false;
+ SetTypeAndIsNull(StorageType.Time, false);
+ _value._timeInfo.FromTimeSpanAndScale(timeSpan, scale);
}
-
- internal void SetToTime(ReadOnlySpan bytes, byte scale, byte denormalizedScale)
+
+ internal void SetToVectorInfo(int elementCount, byte elementType, bool isNull)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
+ SetTypeAndIsNull(StorageType.Vector, isNull);
+ _value._vectorInfo._elementCount = elementCount;
+ _value._vectorInfo._elementType = elementType;
+ }
- _type = StorageType.Time;
- FillInTimeInfo(ref _value._timeInfo, bytes, scale, denormalizedScale);
- _isNull = false;
+ #endregion
+
+ #region Private Helpers
+
+ private static SqlMoney GetSqlMoneyFromLong(long value)
+ {
+ #if NET
+ return SqlMoney.FromTdsValue(value);
+ #else
+ return SqlTypeWorkarounds.SqlMoneyCtor(value, 1);
+ #endif
}
- internal void SetToTime(TimeSpan timeSpan, byte scale)
+ private ISqlVector GetSqlVector()
+ {
+ MetaType.SqlVectorElementType elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType;
+ switch (elementType)
+ {
+ case MetaType.SqlVectorElementType.Float32:
+ return GetSqlVector();
+ default:
+ throw SQL.VectorTypeNotSupported(elementType.ToString());
+ }
+ }
+
+ private T GetValue(StorageType storageType, T value)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
+ ThrowIfNull();
- _type = StorageType.Time;
- _value._timeInfo._ticks = timeSpan.Ticks;
- _value._timeInfo._scale = scale;
- _isNull = false;
+ return _type == storageType
+ ? value
+ : (T)Value; // Types we cannot directly convert to (ie, everything except for
+ // `storageType` will need to converted via boxing.
+ }
+
+ private TOut GetValueAs(TValue value)
+ {
+ // [Field]As method explanation:
+ // these methods are used to bridge generic to non-generic access to value type fields on the storage struct
+ // 1) where typeof(T) == typeof(field)
+ // 1) RyuJIT will recognize the pattern of (T)(object)T as being redundant and eliminate
+ // the T and object casts leaving T, so while this looks like it will put every value type instance in a box the
+ // generated assembly will be short and direct
+ // 2) another jit may not recognize the pattern and should emit the code as seen. this will box and then unbox the
+ // value type which is no worse than the mechanism that this code replaces
+ // 2) where typeof(T) != typeof(field)
+ // the jit will emit all the cast operations as written. this will put the value into a box and then attempt to
+ // cast it, because it is an object no conversions are used and this will generate the desired InvalidCastException
+ // for example users cannot widen a short to an int preserving external expectations
+
+ ThrowIfNull();
+ return (TOut)(object)value;
}
- internal void SetToDateTime2(ReadOnlySpan bytes, byte scale, byte denormalizedScale)
+ private void SetObject(StorageType storageType, T value)
+ where T : INullable
{
- Debug.Assert(IsEmpty, "setting value a second time?");
- int length = bytes.Length;
- _type = StorageType.DateTime2;
- FillInTimeInfo(ref _value._dateTime2Info._timeInfo, bytes.Slice(0, length - 3), scale, denormalizedScale); // remaining 3 bytes is for date
- _value._dateTime2Info._date = GetDateFromByteArray(bytes.Slice(length - 3)); // 3 bytes for date
- _isNull = false;
+ SetTypeAndIsNull(storageType, value.IsNull);
+ _object = value;
}
-
- internal void SetToDateTimeOffset(ReadOnlySpan bytes, byte scale, byte denormalizedScale)
+
+ private void SetValue(StorageType storageType, ref T valueField, T value)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
- int length = bytes.Length;
- _type = StorageType.DateTimeOffset;
- FillInTimeInfo(ref _value._dateTimeOffsetInfo._dateTime2Info._timeInfo, bytes.Slice(0, length - 5), scale, denormalizedScale); // remaining 5 bytes are for date and offset
- _value._dateTimeOffsetInfo._dateTime2Info._date = GetDateFromByteArray(bytes.Slice(length - 5)); // 3 bytes for date
- _value._dateTimeOffsetInfo._offset = (short)(bytes[length - 2] + (bytes[length - 1] << 8)); // 2 bytes for offset (Int16)
- _isNull = false;
+ SetTypeAndIsNull(storageType, false);
+ valueField = value;
}
- internal void SetToDateTimeOffset(DateTimeOffset dateTimeOffset, byte scale)
+ private void SetTypeAndIsNull(StorageType storageType, bool isNull)
{
- Debug.Assert(IsEmpty, "setting value a second time?");
+ Debug.Assert(IsEmpty, "Value is being set a second time.");
- _type = StorageType.DateTimeOffset;
- DateTime utcDateTime = dateTimeOffset.UtcDateTime; // timeInfo stores the utc datetime of a datatimeoffset
- _value._dateTimeOffsetInfo._dateTime2Info._timeInfo._ticks = utcDateTime.TimeOfDay.Ticks;
- _value._dateTimeOffsetInfo._dateTime2Info._timeInfo._scale = scale;
- _value._dateTimeOffsetInfo._dateTime2Info._date = utcDateTime.Subtract(DateTime.MinValue).Days;
- _value._dateTimeOffsetInfo._offset = (short)dateTimeOffset.Offset.TotalMinutes;
- _isNull = false;
+ IsNull = isNull;
+ _type = storageType;
}
-
- private static void FillInTimeInfo(ref TimeInfo timeInfo, ReadOnlySpan timeBytes, byte scale, byte denormalizedScale)
+
+ private void ThrowIfNull()
{
- int length = timeBytes.Length;
- Debug.Assert(3 <= length && length <= 5, "invalid data length for timeInfo: " + length);
- Debug.Assert(0 <= scale && scale <= 7, "invalid scale: " + scale);
- Debug.Assert(0 <= denormalizedScale && denormalizedScale <= 7, "invalid denormalized scale: " + denormalizedScale);
-
- long tickUnits = timeBytes[0] + ((long)timeBytes[1] << 8) + ((long)timeBytes[2] << 16);
- if (length > 3)
+ if (IsNull)
+ {
+ throw new SqlNullValueException();
+ }
+ }
+
+ #endregion
+
+ #region Private Structs
+
+ ///
+ /// Used to store DATETIME information.
+ ///
+ private struct DateTimeInfo
+ {
+ ///
+ /// Number of days since 1900-00-00 MINUS 53690.
+ ///
+ internal int DayPart { get; set; }
+
+ ///
+ /// Number of SQL ticks since 00:00:00.
+ /// Note: this is not the same as CLR ticks.
+ ///
+ internal int TimePart { get; set; }
+
+ internal void FromDateTimeData(int dayPart, int timePart)
{
- tickUnits += ((long)timeBytes[3] << 24);
+ DayPart = dayPart;
+ TimePart = timePart;
}
- if (length > 4)
+
+ ///
+ /// Generates a new DateTime object from the SQL DATETIME information.
+ ///
+ internal DateTime ToDateTime()
{
- tickUnits += ((long)timeBytes[4] << 32);
+ // SQL DATETIME is represented as two integers, the number of days since 1900-01-01
+ // and the number of (SQL) ticks since 00:00:00.
+
+ // Values that come from SqlDateTime
+ const double SqlTicksPerMillisecond = 0.3;
+ const int SqlTicksPerSecond = 300;
+ const int SqlTicksPerMinute = SqlTicksPerSecond * 60;
+ const int SqlTicksPerHour = SqlTicksPerMinute * 60;
+ const int SqlTicksPerDay = SqlTicksPerHour * 24;
+
+ // This is added to date to bring negative days up to 0.
+ const uint MinDays = 53690;
+
+ // 9999-12-31 (max date) is this many days after 1900-01-01 (min date)
+ const uint MaxDays = 2958463;
+ const uint OffsetMaxDays = MaxDays + MinDays;
+
+ // Maximum time that can be stored in a DateTime (ie, 23:59:59.997)
+ const uint MaxTicks = SqlTicksPerDay - 1;
+
+ // Number of ticks to add to a new DateTime to get to 1900-01-01
+ const long BaseDateTicks = 599266080000000000L;
+
+ // 1) Check boundaries
+ // a) Days must be: min_days < days < max_days
+ // Which can be simplified to: 0 < days+min_days < max_days+min_days
+ // If days+min_days is still negative, casting to uint will cause it to
+ // overflow, simplifying to: uint(days+min_days) < max_days+min_days
+ // b) Time must be: 0 < time < max_time
+ // If time is negative, casting to uint will cause it to overflow, simplifying
+ // to: uint(time) < max_time
+ if ((uint)(DayPart + MinDays) > OffsetMaxDays ||
+ (uint)TimePart > MaxTicks)
+ {
+ throw SQL.DateTimeOverflow();
+ }
+
+ // 2) Calculate (CLR) ticks in the days
+ long dayTicks = DayPart * TimeSpan.TicksPerDay;
+
+ // 3) Calculate (CLR) ticks in time part
+ double timeInMilliseconds = (TimePart / SqlTicksPerMillisecond) + 0.5;
+ long timeTicks = (long)timeInMilliseconds * TimeSpan.TicksPerMillisecond;
+
+ // 4) Combine ticks and generate DateTime object
+ long totalTicks = BaseDateTicks + dayTicks + timeTicks;
+ return new DateTime(totalTicks);
}
- timeInfo._ticks = tickUnits * TdsEnums.TICKS_FROM_SCALE[scale];
- // Once the deserialization has been completed using the value scale, we need to set the actual denormalized scale,
- // coming from the data type, on the original result, so that it has the proper scale setting.
- // This only applies for values that got serialized/deserialized for encryption. Otherwise, both scales should be equal.
- timeInfo._scale = denormalizedScale;
+ ///
+ /// Generates a new SqlDateTime object from the SQL DATETIME information.
+ ///
+ internal SqlDateTime ToSqlDateTime() =>
+ new SqlDateTime(DayPart, TimePart);
}
-
- private static int GetDateFromByteArray(ReadOnlySpan buf)
+
+ ///
+ /// Used to store DATETIME2 information.
+ ///
+ private struct DateTime2Info
{
- byte thirdByte = buf[2]; // reordered to optimize JIT generated bounds checks to a single instance, review generated asm before changing
- return buf[0] + (buf[1] << 8) + (thirdByte << 16);
- }
+ // @TODO: Move to properties
+
+ ///
+ /// Number of days since 0001-01-01.
+ ///
+ internal int _date;
+
+ ///
+ /// Time component of the DATETIME2 value.
+ ///
+ internal TimeInfo _timeInfo;
- private void ThrowIfNull()
- {
- if (IsNull)
+ internal void FromByteArray(ReadOnlySpan bytes, byte scale, byte denormalizedScale)
{
- throw new SqlNullValueException();
+ int length = bytes.Length;
+
+ // Set time from time bytes
+ ReadOnlySpan timeBytes = bytes.Slice(0, length - 3);
+ _timeInfo.FromByteArray(timeBytes, scale, denormalizedScale);
+
+ // Set days from day bytes
+ // NOTE: Reordered to optimize JIT generated bounds checks to a single instance,
+ // review generated asm before changing.
+ // @TODO: Verify that ^^^ is still accurate/needed
+ ReadOnlySpan dateBytes = bytes.Slice(length - 3);
+ byte thirdByte = dateBytes[2];
+ _date = dateBytes[0] + (dateBytes[1] << 8) + (thirdByte << 16);
+ }
+
+ internal void FromDateTimeAndScale(DateTime dateTime, byte scale)
+ {
+ _date = dateTime.Subtract(DateTime.MinValue).Days;
+ _timeInfo.FromTimeSpanAndScale(dateTime.TimeOfDay, scale);
+ }
+
+ ///
+ /// Generates a new DateTime object from the SQL DATETIME2 information.
+ ///
+ internal DateTime ToDateTime()
+ {
+ long ticks = _date * TimeSpan.TicksPerDay + _timeInfo._ticks;
+ return new DateTime(ticks);
}
}
- // [Field]As method explanation:
- // these methods are used to bridge generic to non-generic access to value type fields on the storage struct
- // where typeof(T) == typeof(field)
- // 1) RyuJIT will recognize the pattern of (T)(object)T as being redundant and eliminate
- // the T and object casts leaving T, so while this looks like it will put every value type instance in a box the
- // generated assembly will be short and direct
- // 2) another jit may not recognize the pattern and should emit the code as seen. this will box and then unbox the
- // value type which is no worse than the mechanism that this code replaces
- // where typeof(T) != typeof(field)
- // the jit will emit all the cast operations as written. this will put the value into a box and then attempt to
- // cast it, because it is an object no conversions are used and this will generate the desired InvalidCastException
- // for example users cannot widen a short to an int preserving external expectations
-
- internal T ByteAs()
+
+ ///
+ /// Used to store DATETIMEOFFSET information.
+ ///
+ private struct DateTimeOffsetInfo
{
- ThrowIfNull();
- return (T)(object)_value._byte;
- }
+ // @TODO: Move to properties
+
+ ///
+ /// DateTime component of the DATETIMEOFFSET value.
+ ///
+ internal DateTime2Info _dateTime2Info;
+
+ ///
+ /// Timezone offset component of the DATETIMEOFFSET value, in minutes.
+ ///
+ internal short _offset;
- internal T BooleanAs()
- {
- ThrowIfNull();
- return (T)(object)_value._boolean;
- }
+ internal void FromByteArray(ReadOnlySpan bytes, byte scale, byte denormalizedScale)
+ {
+ int length = bytes.Length;
+
+ // Set DATETIME2 info from bytes
+ ReadOnlySpan dateTime2Bytes = bytes.Slice(0, length - 2);
+ _dateTime2Info.FromByteArray(dateTime2Bytes, scale, denormalizedScale);
+
+ // Set offset from remaining bytes
+ ReadOnlySpan offsetBytes = bytes.Slice(length - 2);
+ _offset = BinaryPrimitives.ReadInt16LittleEndian(offsetBytes);
+ }
- internal T Int32As()
- {
- ThrowIfNull();
- return (T)(object)_value._int32;
+ internal void FromDateTimeOffsetAndScale(DateTimeOffset dateTimeOffset, byte scale)
+ {
+ _dateTime2Info.FromDateTimeAndScale(dateTimeOffset.UtcDateTime, scale);
+ _offset = (short)dateTimeOffset.Offset.TotalMinutes;
+ }
+
+ ///
+ /// Generates a new DateTimeOffset object from the SQL DATETIMEOFFSET information.
+ ///
+ internal DateTimeOffset ToDateTimeOffset()
+ {
+ DateTime dateTime = _dateTime2Info.ToDateTime();
+ TimeSpan offset = new TimeSpan(0, _offset, 0);
+ return new DateTimeOffset(dateTime, offset);
+ }
}
-
- internal T Int16As()
+
+ ///
+ /// Used to store DECIMAL/NUMERIC type information.
+ ///
+ private struct NumericInfo
{
- ThrowIfNull();
- return (T)(object)_value._int16;
- }
+ ///
+ /// Low 32 bits of the integer value.
+ ///
+ internal int _data1;
+
+ ///
+ /// Middle 32 bits of the integer value.
+ ///
+ internal int _data2;
+
+ ///
+ /// High 32 bits of the integer value.
+ ///
+ internal int _data3;
+
+ ///
+ /// Extended 32 bits of the integer value.
+ ///
+ internal int _data4;
+
+ ///
+ /// Prevision of the value (ie, the number of significant digits to retain). Minimum of
+ /// 1, maximum of 38.
+ ///
+ internal byte _precision;
+
+ ///
+ /// Scale to apply to the integer value (ie, the integer will be multiplied by 1/10^x).
+ ///
+ internal byte _scale;
+
+ ///
+ /// Whether the number is positive or negative.
+ ///
+ internal bool _positive;
- internal T Int64As()
- {
- ThrowIfNull();
- return (T)(object)_value._int64;
- }
+ internal void FromDecimalData(byte precision, byte scale, bool positive, int[] integerBlocks)
+ {
+ Debug.Assert(integerBlocks.Length == 4, "Integer blocks must contain low, mid, high, and extended bits");
+
+ _precision = precision;
+ _scale = scale;
+ _positive = positive;
+ _data1 = integerBlocks[0];
+ _data2 = integerBlocks[1];
+ _data3 = integerBlocks[2];
+ _data4 = integerBlocks[3];
+ }
+
+ internal decimal ToDecimal()
+ {
+ // SQL DECIMAL/NUMERIC type can store larger numbers than CLR decimal type, if we
+ // cannot directly represent the number as a CLR decimal, we can try some tricks to
+ // optimize storage before giving up.
+
+ // 1) Determine if number can fit within a CLR decimal and directly convert if so.
+ if (_data4 == 0 && _scale <= 28)
+ {
+ return new decimal(_data1, _data2, _data3, !_positive, _scale);
+ }
+
+ // 2) Number cannot fit in a CLR decimal, attempt optimization of the value
+ if (_scale > 0)
+ {
+ // 2.1) Find trailing zeroes and actual precision
+ (int trailingZeroes, int actualPrecision) = FindTrailingZeroesAndPrecision();
+
+ // 2.2) Calculate minimum scale after removing trailing zeroes
+ int minimumScale = _scale - trailingZeroes;
+
+ // 2.3) Check if value fits in CLR decimal after optimization
+ if (trailingZeroes > 0 && minimumScale <= 28 && actualPrecision <= 29)
+ {
+ // We can indeed optimize to fit in CLR decimal!
+ // 2.3.1) Calculate target precision for conversion
+ int integerDigits = actualPrecision - minimumScale;
+ int targetPrecision = 29; // Default to maximum precision
+
+ // 2.3.2) Adjust precision based on integer value size.
+ if (integerDigits != 1 && actualPrecision != 29)
+ {
+ // Integer value is not 1 digit (cannot be zero), and we're not already at
+ // maximum precision. Use 28 for target precision to allow for potential
+ // growth.
+ targetPrecision = 28;
+ }
+
+ // 2.3.3 Use SqlDecimal to convert to target precision/scale
+ try
+ {
+ int targetScale = targetPrecision - integerDigits;
+ SqlDecimal sqlValue = ToSqlDecimal();
+ return SqlDecimal.ConvertToPrecScale(sqlValue, targetPrecision, targetScale).Value;
+ }
+ catch (OverflowException)
+ {
+ throw new OverflowException(SQLResource.ConversionOverflowMessage);
+ }
+ }
+ }
+
+ // 3) Optimization was not possible
+ throw new OverflowException(SQLResource.ConversionOverflowMessage);
+ }
+
+ internal SqlDecimal ToSqlDecimal() =>
+ new SqlDecimal(_precision, _scale, _positive, _data1, _data2, _data3, _data4);
+
+ private (int trailingZeroes, int valuableDigits) FindTrailingZeroesAndPrecision()
+ {
+ // Make local copy of the data so we do not modify the internal data.
+ Span integerData = stackalloc uint[] { (uint)_data1, (uint)_data2, (uint)_data3, (uint)_data4 };
+
+ // Repeatedly divide by 10 to determine how many digits are trailing zeroes and how
+ // many are non-zero
+ int length = 4; // Number of data blocks that will be used in the calculation
+ int trailingZeroes = 0; // Number of trailing zeroes
+ int valuableDigits = 0; // Digits in the number that are valuable (non-zero)
+ uint remainder = 0; // Remainder after division by 10
+ while (length > 1 || integerData[0] != 0)
+ {
+ // 1) Divide the number by 10 in-place
+ uint carry = 0;
+ for (int i = length - 1; i >= 0; i--)
+ {
+ ulong accumulator = ((ulong)carry << 32) + integerData[i];
+ integerData[i] = (uint)(accumulator / 10);
+ carry = (uint)(accumulator - integerData[i] * 10);
+ }
+
+ remainder = carry;
+
+ // 2) Normalize the multi-precision number, ie, remove the leading zeroes
+ while (length > 1 && integerData[length - 1] == 0)
+ {
+ length--;
+ }
+
+ // 3) If the working number was divisible by 10, increase trailing zero count.
+ // Otherwise, increase the number of valuable digits.
+ if (remainder == 0 && valuableDigits == 0)
+ {
+ trailingZeroes++;
+ }
+ else
+ {
+ valuableDigits++;
+ }
+ }
+
+ // Handle case where the number divided down to exactly 0. This means all decimal
+ // digits are trailing zeroes.
+ if (remainder == 0)
+ {
+ trailingZeroes = _scale;
+ }
+
+ // Ensure we account for all decimal places defined by the scale.
+ // If we haven't processed enough digits, the remaining are implied leading zeroes,
+ // and we need at least one valuable digit to represent the number.
+ if (trailingZeroes + valuableDigits <= _scale)
+ {
+ valuableDigits = _scale - trailingZeroes + 1;
+ }
- internal T DoubleAs()
+ return (trailingZeroes, valuableDigits);
+ }
+ }
+
+ ///
+ /// This is a hacky way of creating a union type in C#, for optimizing storage of a single
+ /// object of various types. This struct is specifically used for storing value types.
+ ///
+ [StructLayout(LayoutKind.Explicit)]
+ private struct Storage
{
- ThrowIfNull();
- return (T)(object)_value._double;
+ [FieldOffset(0)]
+ internal bool _boolean;
+
+ [FieldOffset(0)]
+ internal byte _byte;
+
+ [FieldOffset(0)]
+ internal DateTimeInfo _dateTimeInfo;
+
+ [FieldOffset(0)]
+ internal double _double;
+
+ [FieldOffset(0)]
+ internal NumericInfo _numericInfo;
+
+ [FieldOffset(0)]
+ internal short _int16;
+
+ [FieldOffset(0)]
+ internal int _int32;
+
+ [FieldOffset(0)]
+ internal long _int64; // also used to store Money, UtcDateTime, Date , and Time
+
+ [FieldOffset(0)]
+ internal Guid _guid;
+
+ [FieldOffset(0)]
+ internal float _single;
+
+ [FieldOffset(0)]
+ internal TimeInfo _timeInfo;
+
+ [FieldOffset(0)]
+ internal DateTime2Info _dateTime2Info;
+
+ [FieldOffset(0)]
+ internal DateTimeOffsetInfo _dateTimeOffsetInfo;
+
+ [FieldOffset(0)]
+ internal VectorInfo _vectorInfo;
}
+
+ ///
+ /// Used to store TIME information.
+ ///
+ private struct TimeInfo
+ {
+ internal long _ticks;
+ internal byte _scale;
- internal T SingleAs()
+ internal void FromByteArray(ReadOnlySpan timeBytes, byte scale, byte denormalizedScale)
+ {
+ // Prefetch the length for guaranteed performance.
+ int length = timeBytes.Length;
+
+ Debug.Assert(length >= 3 && length <= 5, "typeBytes must have 3-5 items in it.");
+ Debug.Assert(scale <= 7, "scale must be less than 8");
+ Debug.Assert(denormalizedScale <= 7, "denormalizedScale mut be less than 8");
+
+ // Deserialize the timeBytes into a long
+ // Note: we cannot use binary primitives here since timeBytes is variable length
+ // and will never be 8 bytes.
+ long tickUnits = 0;
+ for (int i = 0; i < length; i++)
+ {
+ tickUnits += (long)timeBytes[i] << (8 * i);
+ }
+
+ // Calculate true ticks from deserialized value and scale
+ _ticks = tickUnits * TdsEnums.TICKS_FROM_SCALE[scale];
+
+ // Once the deserialization has been completed using the value scale, we need to
+ // set the actual denormalized scale, coming from the data type, on the original
+ // result, so that it has the proper scale setting. This only applies for values
+ // that got serialized/deserialized for encryption. Otherwise, both scales should
+ // be equal.
+ _scale = denormalizedScale;
+ }
+
+ internal void FromTimeSpanAndScale(TimeSpan timeSpan, byte scale)
+ {
+ _ticks = timeSpan.Ticks;
+ _scale = scale;
+ }
+ }
+
+ private struct VectorInfo
{
- ThrowIfNull();
- return (T)(object)_value._single;
+ internal int _elementCount;
+ internal byte _elementType;
+
+ ///
+ /// Constructs a new (null contents) SqlVector of the given type. Instance will have
+ /// count equal to .
+ ///
+ internal SqlVector ToNull()
+ where T : unmanaged =>
+ new SqlVector(_elementCount);
}
+
+ #endregion
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index d1f3f5c1a5..92d278a3e3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -3161,7 +3161,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met
// this block of type specific shortcuts uses RyuJIT jit behaviors to achieve fast implementations of the primitive types
// RyuJIT will be able to determine at compilation time that the typeof(T)==typeof() options are constant
// and be able to remove all implementations which cannot be reached. this will eliminate non-specialized code for
- Type dataType = data.GetTypeFromStorageType(false);
+ Type dataType = data.ClrType;
if (typeof(T) == typeof(int) && dataType == typeof(int))
{
return data.Int32As();
@@ -3365,10 +3365,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met
{
if (typeof(T) == typeof(string) && metaData.metaType.SqlDbType == SqlDbTypeExtensions.Vector)
{
- if (data.IsNull)
- return (T)(object)data.String;
- else
- return (T)(object)data.GetSqlVector().GetString();
+ return (T)(object)data.String;
}
// the requested type is likely to be one that isn't supported so try the cast and
// unless there is a null value conversion then feedback the cast exception with
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
index f9b940fec3..b750d7e859 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
@@ -736,18 +736,14 @@ public override object Value
{
return _value;
}
- else if (_sqlBufferReturnValue != null)
+
+ if (_sqlBufferReturnValue != null)
{
- if (ParameterIsSqlType)
- {
- if (_sqlBufferReturnValue.VariantInternalStorageType == SqlBuffer.StorageType.Vector)
- {
- return GetVectorReturnValue();
- }
- return _sqlBufferReturnValue.SqlValue;
- }
- return _sqlBufferReturnValue.Value;
+ return ParameterIsSqlType
+ ? _sqlBufferReturnValue.SqlValue
+ : _sqlBufferReturnValue.Value;
}
+
return null;
}
set
@@ -763,30 +759,6 @@ public override object Value
}
}
- private object GetVectorReturnValue()
- {
- var elementType = (MetaType.SqlVectorElementType)_sqlBufferReturnValue.GetVectorInfo()._vectorInfo._elementType;
- int elementCount = _sqlBufferReturnValue.GetVectorInfo()._vectorInfo._elementCount;
-
- if (IsNull)
- {
- switch (elementType)
- {
- case MetaType.SqlVectorElementType.Float32:
- return new SqlVector(elementCount);
- default:
- throw SQL.VectorTypeNotSupported(elementType.ToString());
- }
- }
- switch (elementType)
- {
- case MetaType.SqlVectorElementType.Float32:
- return new SqlVector((byte[])_sqlBufferReturnValue.Value);
- default:
- throw SQL.VectorTypeNotSupported(elementType.ToString());
- }
- }
-
///
[
RefreshProperties(RefreshProperties.All),
@@ -1953,8 +1925,12 @@ private MetaType GetMetaTypeOnly()
return MetaType.GetMetaTypeFromType(valueType);
}
else if (_sqlBufferReturnValue != null)
- { // value came back from the server
- Type valueType = _sqlBufferReturnValue.GetTypeFromStorageType(HasFlag(SqlParameterFlags.IsSqlParameterSqlType));
+ {
+ // value came back from the server
+ Type valueType = HasFlag(SqlParameterFlags.IsSqlParameterSqlType)
+ ? _sqlBufferReturnValue.SqlType
+ : _sqlBufferReturnValue.ClrType;
+
if (valueType != null)
{
return MetaType.GetMetaTypeFromType(valueType);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.cs
index 853be887dc..8402b400e2 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.cs
@@ -52,43 +52,5 @@ internal static XmlReader SqlXmlCreateSqlXmlReader(TextReader textReader, bool c
return XmlReader.Create(textReader, settingsToUse);
}
#endregion
-
- #region Work around inability to access SqlDateTime.ToDateTime
- internal static DateTime SqlDateTimeToDateTime(int daypart, int timepart)
- {
- // Values need to match those from SqlDateTime
- const double SQLTicksPerMillisecond = 0.3;
- const int SQLTicksPerSecond = 300;
- const int SQLTicksPerMinute = SQLTicksPerSecond * 60;
- const int SQLTicksPerHour = SQLTicksPerMinute * 60;
- const int SQLTicksPerDay = SQLTicksPerHour * 24;
- //const int MinDay = -53690; // Jan 1 1753
- const uint MinDayOffset = 53690; // postive value of MinDay used to pull negative values up to 0 so a single check can be used
- const uint MaxDay = 2958463; // Dec 31 9999 is this many days from Jan 1 1900
- const uint MaxTime = SQLTicksPerDay - 1; // = 25919999, 11:59:59:997PM
- const long BaseDateTicks = 599266080000000000L;//new DateTime(1900, 1, 1).Ticks;
-
- // casting to uint wraps negative values to large positive ones above the valid
- // ranges so the lower bound doesn't need to be checked
- if ((uint)(daypart + MinDayOffset) > (MaxDay + MinDayOffset) || (uint)timepart > MaxTime)
- {
- ThrowOverflowException();
- }
-
- long dayticks = daypart * TimeSpan.TicksPerDay;
- double timePartPerMs = timepart / SQLTicksPerMillisecond;
- timePartPerMs += 0.5;
- long timeTicks = ((long)timePartPerMs) * TimeSpan.TicksPerMillisecond;
- long totalTicks = BaseDateTicks + dayticks + timeTicks;
- return new DateTime(totalTicks);
- }
-
- // this method is split out of SqlDateTimeToDateTime for performance reasons
- // it is faster to make a method call than it is to incorporate the asm for this
- // method in the calling method.
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static Exception ThrowOverflowException() => throw SQL.DateTimeOverflow();
-
- #endregion
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs
index e63a5b7462..e03a24bab4 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs
@@ -85,14 +85,11 @@ internal SqlVector(byte[] tdsBytes)
#region Methods
- internal string GetString()
- {
- if (IsNull)
- {
- return SQLResource.NullString;
- }
- return JsonSerializer.Serialize(Memory);
- }
+ ///
+ string ISqlVector.GetString() =>
+ IsNull
+ ? SQLResource.NullString
+ : JsonSerializer.Serialize(Memory);
#endregion
@@ -112,12 +109,15 @@ internal string GetString()
///
public ReadOnlyMemory Memory { get; init; }
- #endregion
+ ///
+ byte ISqlVector.ElementSize => _elementSize;
- #region ISqlVector Internal Properties
+ ///
byte ISqlVector.ElementType => _elementType;
- byte ISqlVector.ElementSize => _elementSize;
+
+ ///
byte[] ISqlVector.VectorPayload => _tdsBytes;
+
#endregion
#region Helpers
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlVectorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs
similarity index 56%
rename from src/Microsoft.Data.SqlClient/tests/UnitTests/SqlVectorTest.cs
rename to src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs
index 3390d95c02..d7e118ac87 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlVectorTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs
@@ -32,190 +32,240 @@ public void Construct_Length_Negative()
[Fact]
public void Construct_Length()
{
+ // Act
var vec = new SqlVector(5);
+
+ // Assert
+ // - SqlVector properties/methods
Assert.True(vec.IsNull);
Assert.Equal(5, vec.Length);
Assert.Equal(28, vec.Size);
+
// Note that ReadOnlyMemory<> equality checks that both instances point
// to the same memory. We want to check memory content equality, so we
// compare their arrays instead.
Assert.Equal(new ReadOnlyMemory().ToArray(), vec.Memory.ToArray());
- Assert.Equal(SQLResource.NullString, vec.GetString());
- var ivec = vec as ISqlVector;
+ // - ISqlVector properties/methods
+ ISqlVector ivec = vec;
Assert.Equal(0x00, ivec.ElementType);
Assert.Equal(0x04, ivec.ElementSize);
Assert.Empty(ivec.VectorPayload);
+
+ Assert.Equal(SQLResource.NullString, ivec.GetString());
}
[Fact]
public void Construct_WithLengthZero()
{
+ // Act
var vec = new SqlVector(0);
+
+ // Assert
+ // - SqlVector properties/methods
Assert.True(vec.IsNull);
Assert.Equal(0, vec.Length);
Assert.Equal(8, vec.Size);
+
// Note that ReadOnlyMemory<> equality checks that both instances point
// to the same memory. We want to check memory content equality, so we
// compare their arrays instead.
Assert.Equal(new ReadOnlyMemory().ToArray(), vec.Memory.ToArray());
- Assert.Equal(SQLResource.NullString, vec.GetString());
- var ivec = vec as ISqlVector;
+ // - ISqlVector properties/methods
+ ISqlVector ivec = vec;
Assert.Equal(0x00, ivec.ElementType);
Assert.Equal(0x04, ivec.ElementSize);
Assert.Empty(ivec.VectorPayload);
+
+ Assert.Equal(SQLResource.NullString, ivec.GetString());
}
[Fact]
public void Construct_Memory_Empty()
{
- SqlVector vec = new(new ReadOnlyMemory());
+ // Act
+ SqlVector vec = new SqlVector(new ReadOnlyMemory());
+
+ // Assert
+ // - SqlVector properties/methods
Assert.False(vec.IsNull);
Assert.Equal(0, vec.Length);
Assert.Equal(8, vec.Size);
Assert.Equal(new ReadOnlyMemory().ToArray(), vec.Memory.ToArray());
- Assert.Equal("[]", vec.GetString());
- var ivec = vec as ISqlVector;
+ // - ISqlVector properties/methods
+ ISqlVector ivec = vec;
Assert.Equal(0x00, ivec.ElementType);
Assert.Equal(0x04, ivec.ElementSize);
- Assert.Equal(
- new byte[] { 0xA9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
- ivec.VectorPayload);
+ Assert.Equal(new byte[] { 0xA9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, ivec.VectorPayload);
+
+ Assert.Equal("[]", ivec.GetString());
}
[Fact]
public void Construct_Memory()
{
+ // Arrange
float[] data = [1.1f, 2.2f];
- ReadOnlyMemory memory = new(data);
- SqlVector vec = new(memory);
+ ReadOnlyMemory memory = new ReadOnlyMemory(data);
+
+ // Act
+ SqlVector vec = new SqlVector(memory);
+
+ // Assert
+ // - SqlVector methods/properties
Assert.False(vec.IsNull);
Assert.Equal(2, vec.Length);
Assert.Equal(16, vec.Size);
Assert.Equal(memory.ToArray(), vec.Memory.ToArray());
Assert.Equal(data, vec.Memory.ToArray());
- #if NETFRAMEWORK
- Assert.Equal("[1.10000002,2.20000005]", vec.GetString());
- #else
- Assert.Equal("[1.1,2.2]", vec.GetString());
- #endif
- var ivec = vec as ISqlVector;
+
+ // - ISqlVector methods/properties
+ ISqlVector ivec = vec;
Assert.Equal(0x00, ivec.ElementType);
Assert.Equal(0x04, ivec.ElementSize);
Assert.Equal(
- MakeTdsPayload(
- new byte[] { 0xA9, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 },
- memory),
+ MakeTdsPayload(new byte[] { 0xA9, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 }, memory),
ivec.VectorPayload);
+
+ #if NETFRAMEWORK
+ Assert.Equal("[1.10000002,2.20000005]", ivec.GetString());
+ #else
+ Assert.Equal("[1.1,2.2]", ivec.GetString());
+ #endif
}
[Fact]
public void Construct_Memory_ImplicitConversionFromFloatArray()
{
- float[] data = new float[] { 3.3f, 4.4f, 5.5f };
+ // Arrange
+ float[] data = new[] { 3.3f, 4.4f, 5.5f };
+
+ // Act
var vec = new SqlVector(data);
+
+ // Assert
+ // - SqlVector methods/properties
Assert.False(vec.IsNull);
Assert.Equal(3, vec.Length);
Assert.Equal(20, vec.Size);
Assert.Equal(new ReadOnlyMemory(data).ToArray(), vec.Memory.ToArray());
Assert.Equal(data, vec.Memory.ToArray());
- #if NETFRAMEWORK
- Assert.Equal("[3.29999995,4.4000001,5.5]", vec.GetString());
- #else
- Assert.Equal("[3.3,4.4,5.5]", vec.GetString());
- #endif
-
- var ivec = vec as ISqlVector;
+
+ // - ISqlVector methods/properties
+ ISqlVector ivec = vec;
Assert.Equal(0x00, ivec.ElementType);
Assert.Equal(0x04, ivec.ElementSize);
Assert.Equal(
- MakeTdsPayload(
- new byte[] { 0xA9, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 },
- data),
+ MakeTdsPayload(new byte[] { 0xA9, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, data),
ivec.VectorPayload);
+
+ #if NETFRAMEWORK
+ Assert.Equal("[3.29999995,4.4000001,5.5]", ivec.GetString());
+ #else
+ Assert.Equal("[3.3,4.4,5.5]", ivec.GetString());
+ #endif
}
[Fact]
public void Construct_Bytes()
{
+ // Arrange
+ byte[] header = new byte[] { 0xA9, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 };
float[] data = new float[] { 6.6f, 7.7f };
- var bytes =
- MakeTdsPayload(
- new byte[] { 0xA9, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 },
- data);
+ byte[] bytes = MakeTdsPayload(header, data);
+ // Act
var vec = new SqlVector(bytes);
+
+ // Assert
+ // - SqlVector methods/properties
Assert.False(vec.IsNull);
Assert.Equal(2, vec.Length);
Assert.Equal(16, vec.Size);
Assert.Equal(new ReadOnlyMemory(data).ToArray(), vec.Memory.ToArray());
Assert.Equal(data, vec.Memory.ToArray());
- #if NETFRAMEWORK
- Assert.Equal("[6.5999999,7.69999981]", vec.GetString());
- #else
- Assert.Equal("[6.6,7.7]", vec.GetString());
- #endif
-
- var ivec = vec as ISqlVector;
+
+ // - ISqlVector methods/properties
+ ISqlVector ivec = vec;
Assert.Equal(0x00, ivec.ElementType);
Assert.Equal(0x04, ivec.ElementSize);
Assert.Equal(bytes, ivec.VectorPayload);
+
+ #if NETFRAMEWORK
+ Assert.Equal("[6.5999999,7.69999981]", ivec.GetString());
+ #else
+ Assert.Equal("[6.6,7.7]", ivec.GetString());
+ #endif
}
[Fact]
public void Construct_Bytes_ShortHeader()
{
- Assert.Throws(() =>
- {
- new SqlVector(new byte[] { 0xA9, 0x01, 0x00, 0x00 });
- });
+ // Arrange
+ var tdsBytes = new byte[] { 0xA9, 0x01, 0x00, 0x00 };
+
+ // Act
+ Action action = () => _ = new SqlVector(tdsBytes);
+
+ // Assert
+ Assert.Throws(action);
}
[Fact]
public void Construct_Bytes_UnknownMagic()
{
- Assert.Throws(() =>
- {
- new SqlVector(
- new byte[] { 0xA8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
- });
+ // Arrange
+ var tdsBytes = new byte[] { 0xA8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ // Act
+ Action action = () => _ = new SqlVector(tdsBytes);
+
+ // Assert
+ Assert.Throws(action);
}
[Fact]
public void Construct_Bytes_UnsupportedVersion()
{
- Assert.Throws(() =>
- {
- new SqlVector(
- new byte[] { 0xA9, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
- });
+ // Arrange
+ var tdsBytes = new byte[] { 0xA9, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ // Act
+ Action action = () => _ = new SqlVector(tdsBytes);
+
+ // Assert
+ Assert.Throws(action);
}
[Fact]
public void Construct_Bytes_TypeMismatch()
{
- Assert.Throws(() =>
- {
- new SqlVector(
- new byte[] { 0xA9, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 });
- });
+ // Arrange
+ var tdsBytes = new byte[] { 0xA9, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 };
+
+ // Act
+ Action action = () => _ = new SqlVector(tdsBytes);
+
+ // Assert
+ Assert.Throws(action);
}
[Fact]
public void Construct_Bytes_LengthMismatch()
{
- // The header indicates 2 elements, but the payload has 3 floats.
+ // Arrange
+ // - The header indicates 2 elements, but the payload has 3 floats.
var header = new byte[] { 0xA9, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 };
- var bytes = MakeTdsPayload(
- header,
- new ReadOnlyMemory(new float[] { 1.1f, 2.2f, 3.3f }));
+ var floats = new ReadOnlyMemory([1.1f, 2.2f, 3.3f]);
+ var bytes = MakeTdsPayload(header, floats);
- Assert.Throws(() =>
- {
- new SqlVector(bytes);
- });
+ // Act
+ Action action = () => _ = new SqlVector(bytes);
+
+ // Assert
+ Assert.Throws(action);
}
[Fact]
@@ -228,7 +278,7 @@ public void Null_Property()
#region Helpers
- private byte[] MakeTdsPayload(byte[] header, ReadOnlyMemory values)
+ private static byte[] MakeTdsPayload(byte[] header, ReadOnlyMemory values)
{
int length = header.Length + (values.Length * sizeof(float));
byte[] payload = new byte[length];