diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
index 4b28fd7cc2..ff54d18a17 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
@@ -415,7 +415,7 @@ No conversions are performed; therefore. the data retrieved must already be a ch
- SqlMoney
- SqlSingle
- SqlString
- - SqlVectorFloat32
+ - SqlVector
- Stream
- String
- TextReader
@@ -490,7 +490,7 @@ No conversions are performed; therefore. the data retrieved must already be a ch
- SqlMoney
- SqlSingle
- SqlString
- - SqlVectorFloat32
+ - SqlVector
- Stream
- String
- TextReader
@@ -964,13 +964,13 @@ The method retur
No conversions are performed; therefore, the data retrieved must already be a JSON string, or an exception is generated.
-
+
- Gets the value of the specified column as a .
+ Gets the value of the specified column as a .
- A object representing the column at the given ordinal.
+ A object representing the column at the given ordinal.
The index passed was outside the range of 0 to - 1
@@ -979,12 +979,12 @@ The method retur
An attempt was made to read or access columns in a closed .
- The retrieved data is not compatible with the type.
+ The retrieved data is not compatible with the type.
No conversions are performed; therefore, the data retrieved must already be a vector value, or an exception is generated.
-
+
The zero-based column ordinal.
diff --git a/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml b/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml
new file mode 100644
index 0000000000..0157a05787
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml
@@ -0,0 +1,49 @@
+
+
+
+
+ Represents a vector value in SQL Server.
+
+
+
+
+ Constructs a null vector of the given length. SQL Server requires vector arguments to specify their length even when null.
+
+
+ Vector length must be non-negative.
+
+
+
+
+
+ Constructs a vector with the given values.
+
+
+
+
+
+
+
+ Represents a null instance without any attributes.
+
+
+ This property is provided for compatibility with DataTable.
+ In most cases, prefer using IsNull to check if a SqlVector instance is a null vector.
+ This is equivalent to null value.
+
+
+
+
+ Returns the number of elements in the vector.
+
+
+
+
+ Returns the number of bytes required to represent this vector when communicating with SQL Server.
+
+
+
+ Returns the vector values as a memory region. No copies are made.
+
+
+
diff --git a/doc/snippets/Microsoft.Data.SqlTypes/SqlVectorFloat32.xml b/doc/snippets/Microsoft.Data.SqlTypes/SqlVectorFloat32.xml
deleted file mode 100644
index 2a01f616e4..0000000000
--- a/doc/snippets/Microsoft.Data.SqlTypes/SqlVectorFloat32.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
- Represents the 32-bit float vector datatype in SQL Server.
-
-
-
-
- Constructs a null vector of the given length. SQL Server requires vector arguments to specify their length even when null.
-
-
- Vector column length must be non-negative.
-
-
-
-
-
- Constructs a vector with the given values.
-
-
-
-
-
-
-
- Represents a null instance of the type without any attributes.
-
-
- This is equivalent to the C# null value.
-
-
-
-
- Returns the number of elements in the vector.
-
-
-
-
- Returns the number of bytes required to represent this vector when communicating with SQL Server.
-
-
-
- Returns the vector values as a memory region. No copies are made.
- An array of float32 values as value.
-
-
- Returns a JSON string representation of the vector. A new string is generated each time you call this method.
- A JSON value.
-
-
- Returns the vector values as an array of floats.
- An array of values.
-
-
-
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln
index 223fda48ad..e4d29d999c 100644
--- a/src/Microsoft.Data.SqlClient.sln
+++ b/src/Microsoft.Data.SqlClient.sln
@@ -172,7 +172,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlTypes", "
ProjectSection(SolutionItems) = preProject
..\doc\snippets\Microsoft.Data.SqlTypes\SqlFileStream.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlFileStream.xml
..\doc\snippets\Microsoft.Data.SqlTypes\SqlJson.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlJson.xml
- ..\doc\snippets\Microsoft.Data.SqlTypes\SqlVectorFloat32.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlVectorFloat32.xml
+ ..\doc\snippets\Microsoft.Data.SqlTypes\SqlVector.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlVector.xml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.TestUtilities", "Microsoft.Data.SqlClient\tests\tools\Microsoft.Data.SqlClient.TestUtilities\Microsoft.Data.SqlClient.TestUtilities.csproj", "{89D6D382-9B36-43C9-A912-03802FDA8E36}"
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
index 0b3eacbe20..55b68fd9b3 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -122,27 +122,24 @@ public SqlJson(System.Text.Json.JsonDocument jsonDoc) { }
public override string ToString() { throw null; }
}
- ///
- public sealed class SqlVectorFloat32 : System.Data.SqlTypes.INullable
- {
- ///
- public SqlVectorFloat32(int length) { }
- ///
- public SqlVectorFloat32(System.ReadOnlyMemory values) { }
- ///
+ ///
+ public sealed class SqlVector : System.Data.SqlTypes.INullable
+ where T : unmanaged
+ {
+ ///
+ public SqlVector(int length) { }
+ ///
+ public SqlVector(System.ReadOnlyMemory memory) { }
+ ///
public bool IsNull => throw null;
- ///
- public static SqlVectorFloat32 Null => throw null;
- ///
+ ///
+ public static SqlVector Null => throw null;
+ ///
public int Length { get { throw null; } }
- ///
+ ///
public int Size { get { throw null; } }
- ///
- public System.ReadOnlyMemory Values { get { throw null; } }
- ///
- public override string ToString() { throw null; }
- ///
- public float[] ToArray() { throw null; }
+ ///
+ public System.ReadOnlyMemory Memory { get { throw null; } }
}
}
namespace Microsoft.Data.SqlClient
@@ -1395,8 +1392,8 @@ public override void Close() { }
public virtual object GetSqlValue(int i) { throw null; }
///
public virtual int GetSqlValues(object[] values) { throw null; }
- ///
- public virtual Microsoft.Data.SqlTypes.SqlVectorFloat32 GetSqlVectorFloat32(int i) { throw null; }
+ ///
+ public virtual Microsoft.Data.SqlTypes.SqlVector GetSqlVector(int i) where T : unmanaged { throw null; }
///
public virtual System.Data.SqlTypes.SqlXml GetSqlXml(int i) { throw null; }
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index 4d8c185d38..db60c1c927 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -795,8 +795,8 @@
Microsoft\Data\SqlTypes\SqlJson.cs
-
- Microsoft\Data\SqlTypes\SqlVectorFloat32.cs
+
+ Microsoft\Data\SqlTypes\SqlVector.cs
Resources\ResCategoryAttribute.cs
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index 8c8d17dacc..d7f280ca33 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -1370,8 +1370,8 @@ public override void Close() { }
public virtual object GetSqlValue(int i) { throw null; }
///
public virtual int GetSqlValues(object[] values) { throw null; }
- ///
- public virtual Microsoft.Data.SqlTypes.SqlVectorFloat32 GetSqlVectorFloat32(int i) { throw null; }
+ ///
+ public virtual Microsoft.Data.SqlTypes.SqlVector GetSqlVector(int i) where T : unmanaged { throw null; }
///
public virtual System.Data.SqlTypes.SqlXml GetSqlXml(int i) { throw null; }
///
@@ -2416,26 +2416,23 @@ public SqlJson(System.Text.Json.JsonDocument jsonDoc) { }
public override string ToString() { throw null; }
}
- ///
- public sealed class SqlVectorFloat32 : System.Data.SqlTypes.INullable
- {
- ///
- public SqlVectorFloat32(int length) { }
- ///
- public SqlVectorFloat32(System.ReadOnlyMemory values) { }
- ///
+ ///
+ public sealed class SqlVector : System.Data.SqlTypes.INullable
+ where T : unmanaged
+ {
+ ///
+ public SqlVector(int length) { }
+ ///
+ public SqlVector(System.ReadOnlyMemory memory) { }
+ ///
public bool IsNull => throw null;
- ///
- public static SqlVectorFloat32 Null => throw null;
- ///
+ ///
+ public static SqlVector Null => throw null;
+ ///
public int Length { get { throw null; } }
- ///
+ ///
public int Size { get { throw null; } }
- ///
- public System.ReadOnlyMemory Values { get { throw null; } }
- ///
- public override string ToString() { throw null; }
- ///
- public float[] ToArray() { throw null; }
+ ///
+ public System.ReadOnlyMemory Memory { get { throw null; } }
}
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index 42751b7d9d..1b1625bbb7 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -585,7 +585,7 @@
Microsoft\Data\SqlClient\Server\SqlSer.cs
-
+
Microsoft\Data\SqlClient\ISqlVector.cs
@@ -909,8 +909,8 @@
Microsoft\Data\SqlTypes\SqlJson.cs
-
- Microsoft\Data\SqlTypes\SqlVectorFloat32.cs
+
+ Microsoft\Data\SqlTypes\SqlVector.cs
Resources\ResDescriptionAttribute.cs
@@ -921,7 +921,10 @@
System\IO\StreamExtensions.netfx.cs
-
+
+ System\Runtime\CompilerServices\IsExternalInit.netfx.cs
+
+
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
index c8747af0f9..470cb8e35e 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -1147,12 +1147,6 @@ internal static Exception NullOutputParameterValueForVector(string paramName)
internal static ArgumentException InvalidVectorHeader()
=> Argument(StringsHelper.GetString(Strings.ADP_InvalidVectorHeader));
- internal static ArgumentOutOfRangeException InvalidVectorColumnLength(string paramName)
- => ArgumentOutOfRange(paramName, StringsHelper.GetString(Strings.ADP_InvalidVectorColumnLength));
-
- internal static ArgumentException EmptyVectorValues(string arrayName)
- => Argument(StringsHelper.GetString(Strings.ADP_EmptyVectorValues, arrayName));
-
internal static Exception InvalidJsonStringForVector(string value, Exception inner)
=> InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidJsonStringForVector, value), inner);
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 3a30edac44..ca9fe35743 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ISqlVector.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ISqlVector.cs
@@ -28,7 +28,7 @@ internal interface ISqlVector
byte[] VectorPayload { get; }
///
- /// Gets the raw vector data formatted for TDS payload.
+ /// Returns the total size in bytes for sending SqlVector value on TDS.
///
int Size { get; }
}
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 c4fff6b40a..39d2758d62 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs
@@ -513,7 +513,7 @@ internal string String
switch (elementType)
{
case MetaType.SqlVectorElementType.Float32:
- return SqlVectorFloat32.ToString();
+ return GetSqlVector().GetString();
default:
throw SQL.VectorTypeNotSupported(elementType.ToString());
}
@@ -954,7 +954,14 @@ internal SqlString SqlString
{
return SqlString.Null;
}
- return new SqlString(SqlVectorFloat32.ToString());
+ 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)
@@ -980,14 +987,18 @@ internal SqlString SqlString
internal SqlJson SqlJson => (StorageType.Json == _type) ? (IsNull ? SqlTypes.SqlJson.Null : new SqlJson((string)_object)) : (SqlJson)SqlValue;
- internal SqlVectorFloat32 SqlVectorFloat32 =>
- _type is StorageType.Vector
- ? (
- IsNull
- ? new SqlVectorFloat32(_value._vectorInfo._elementCount)
- : new SqlVectorFloat32(SqlBinary.Value)
- )
- : (SqlVectorFloat32)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
{
@@ -1028,7 +1039,7 @@ internal object SqlValue
switch (elementType)
{
case MetaType.SqlVectorElementType.Float32:
- return SqlVectorFloat32;
+ return GetSqlVector();
default:
throw SQL.VectorTypeNotSupported(elementType.ToString());
}
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 22c2a2da6a..d1f3f5c1a5 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -2835,11 +2835,16 @@ virtual public SqlJson GetSqlJson(int i)
return json;
}
- ///
- virtual public SqlVectorFloat32 GetSqlVectorFloat32(int i)
+ ///
+ virtual public SqlVector GetSqlVector(int i) where T : unmanaged
{
+ if (typeof(T) != typeof(float))
+ {
+ throw SQL.VectorTypeNotSupported(typeof(T).FullName);
+ }
+
ReadColumn(i);
- return _data[i].SqlVectorFloat32;
+ return _data[i].GetSqlVector();
}
///
@@ -3101,7 +3106,7 @@ private object GetValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaDa
switch (metaData.scale)
{
case (byte)MetaType.SqlVectorElementType.Float32:
- return data.SqlVectorFloat32;
+ return data.GetSqlVector();
default:
throw SQL.VectorTypeNotSupported(metaData.scale.ToString());
}
@@ -3211,14 +3216,14 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met
return (T)(object)data.TimeOnly;
}
#endif
- else if (typeof(T) == typeof(SqlVectorFloat32))
+ else if (typeof(T) == typeof(SqlVector))
{
MetaType metaType = metaData.metaType;
if (metaType.SqlDbType != SqlDbTypeExtensions.Vector)
{
throw SQL.VectorNotSupportedOnColumnType(metaData.column);
}
- return (T)(object)data.SqlVectorFloat32;
+ return (T)(object)data.GetSqlVector();
}
else if (typeof(T) == typeof(XmlReader))
{
@@ -3363,7 +3368,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met
if (data.IsNull)
return (T)(object)data.String;
else
- return (T)(object)data.SqlVectorFloat32.ToString();
+ return (T)(object)data.GetSqlVector().GetString();
}
// 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/SqlEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs
index 903dcdb3ed..27b3eebcd0 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs
@@ -64,9 +64,12 @@ internal sealed class MetaType
internal readonly bool Is100Supported;
// SqlVector Element Types
+ //
+ // These underlying values must match the vector "dimension type" values
+ // in the TDS protocol.
internal enum SqlVectorElementType : byte
{
- Float32 = 0x0
+ Float32 = 0x00
}
public MetaType(byte precision, byte scale, int fixedLength, bool isFixed, bool isLong, bool isPlp, byte tdsType, byte nullableTdsType, string typeName,
@@ -377,7 +380,7 @@ private static MetaType GetMetaTypeFromValue(Type dataType, object value, bool i
return MetaXml;
else if (dataType == typeof(SqlJson))
return s_MetaJson;
- else if (dataType == typeof(SqlVectorFloat32))
+ else if (dataType == typeof(SqlVector))
return s_MetaVector;
else if (dataType == typeof(SqlString))
{
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 3f11139cdf..f9b940fec3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs
@@ -773,7 +773,7 @@ private object GetVectorReturnValue()
switch (elementType)
{
case MetaType.SqlVectorElementType.Float32:
- return new SqlVectorFloat32(elementCount);
+ return new SqlVector(elementCount);
default:
throw SQL.VectorTypeNotSupported(elementType.ToString());
}
@@ -781,7 +781,7 @@ private object GetVectorReturnValue()
switch (elementType)
{
case MetaType.SqlVectorElementType.Float32:
- return new SqlVectorFloat32((byte[])_sqlBufferReturnValue.Value);
+ return new SqlVector((byte[])_sqlBufferReturnValue.Value);
default:
throw SQL.VectorTypeNotSupported(elementType.ToString());
}
@@ -2369,7 +2369,7 @@ internal static object CoerceValue(object value, MetaType destinationType, out b
value = ((TimeOnly)value).ToTimeSpan();
}
#endif
- else if (currentType == typeof(SqlVectorFloat32))
+ else if (currentType == typeof(SqlVector))
{
value = ((ISqlVector)value).VectorPayload;
}
@@ -2377,7 +2377,7 @@ internal static object CoerceValue(object value, MetaType destinationType, out b
{
try
{
- value = (new SqlVectorFloat32(JsonSerializer.Deserialize(value as string)) as ISqlVector).VectorPayload;
+ value = (new SqlVector(JsonSerializer.Deserialize(value as string)) as ISqlVector).VectorPayload;
}
catch (Exception ex) when (ex is ArgumentNullException || ex is JsonException)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs
new file mode 100644
index 0000000000..e63a5b7462
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs
@@ -0,0 +1,239 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Buffers.Binary;
+using System.Data.SqlTypes;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text.Json;
+using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient;
+
+#nullable enable
+
+namespace Microsoft.Data.SqlTypes;
+
+///
+public sealed class SqlVector : INullable, ISqlVector
+where T : unmanaged
+{
+ #region Constants
+
+ private const byte VecHeaderMagicNo = 0xA9;
+ private const byte VecVersionNo = 0x01;
+
+ #endregion
+
+ #region Fields
+
+ private readonly byte _elementType;
+ private readonly byte _elementSize;
+ private readonly byte[] _tdsBytes;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ public SqlVector(int length)
+ {
+ if (length < 0)
+ {
+ throw ADP.ArgumentOutOfRange(nameof(length), SQLResource.InvalidArraySizeMessage);
+ }
+
+ (_elementType, _elementSize) = GetTypeFieldsOrThrow();
+
+ IsNull = true;
+
+ Length = length;
+ Size = TdsEnums.VECTOR_HEADER_SIZE + (_elementSize * Length);
+
+ _tdsBytes = Array.Empty();
+ Memory = new();
+ }
+
+ ///
+ public SqlVector(ReadOnlyMemory memory)
+ {
+ (_elementType, _elementSize) = GetTypeFieldsOrThrow();
+
+ IsNull = false;
+
+ Length = memory.Length;
+ Size = TdsEnums.VECTOR_HEADER_SIZE + (_elementSize * Length);
+
+ _tdsBytes = MakeTdsBytes(memory);
+ Memory = memory;
+ }
+
+ internal SqlVector(byte[] tdsBytes)
+ {
+ (_elementType, _elementSize) = GetTypeFieldsOrThrow();
+
+ (Length, Size) = GetCountsOrThrow(tdsBytes);
+
+ IsNull = false;
+
+ _tdsBytes = tdsBytes;
+ Memory = new(MakeArray());
+ }
+
+ #endregion
+
+ #region Methods
+
+ internal string GetString()
+ {
+ if (IsNull)
+ {
+ return SQLResource.NullString;
+ }
+ return JsonSerializer.Serialize(Memory);
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ public bool IsNull { get; init; }
+
+ ///
+ public static SqlVector? Null => null;
+
+ ///
+ public int Length { get; init; }
+ ///
+ public int Size { get; init; }
+
+ ///
+ public ReadOnlyMemory Memory { get; init; }
+
+ #endregion
+
+ #region ISqlVector Internal Properties
+ byte ISqlVector.ElementType => _elementType;
+ byte ISqlVector.ElementSize => _elementSize;
+ byte[] ISqlVector.VectorPayload => _tdsBytes;
+ #endregion
+
+ #region Helpers
+
+ private (byte, byte) GetTypeFieldsOrThrow()
+ {
+ byte elementType;
+ byte elementSize;
+
+ if (typeof(T) == typeof(float))
+ {
+ elementType = (byte)MetaType.SqlVectorElementType.Float32;
+ elementSize = sizeof(float);
+ }
+ else
+ {
+ throw SQL.VectorTypeNotSupported(typeof(T).FullName);
+ }
+
+ return (elementType, elementSize);
+ }
+
+ private byte[] MakeTdsBytes(ReadOnlyMemory values)
+ {
+ //Refer to TDS section 2.2.5.5.7 for vector header format
+ // +------------------------+-----------------+----------------------+------------------+----------------------------+--------------+
+ // | Field | Size (bytes) | Example Value | Description |
+ // +------------------------+-----------------+----------------------+--------------------------------------------------------------+
+ // | Layout Format | 1 | 0xA9 | Magic number indicating vector layout format |
+ // | Layout Version | 1 | 0x01 | Version of the vector format |
+ // | Number of Dimensions | 2 | NN | Number of vector elements |
+ // | Dimension Type | 1 | 0x00 | Element type indicator (e.g. 0x00 for float32) |
+ // | Reserved | 3 | 0x00 0x00 0x00 | Reserved for future use |
+ // | Stream of Values | NN * sizeof(T) | [element bytes...] | Raw bytes for vector elements |
+ // +------------------------+-----------------+----------------------+--------------------------------------------------------------+
+
+ byte[] result = new byte[Size];
+
+ // Header Bytes
+ result[0] = VecHeaderMagicNo;
+ result[1] = VecVersionNo;
+ result[2] = (byte)(Length & 0xFF);
+ result[3] = (byte)((Length >> 8) & 0xFF);
+ result[4] = _elementType;
+ result[5] = 0x00;
+ result[6] = 0x00;
+ result[7] = 0x00;
+
+#if NETFRAMEWORK
+ // Copy data via marshaling.
+ if (MemoryMarshal.TryGetArray(values, out ArraySegment segment))
+ {
+ Buffer.BlockCopy(segment.Array, segment.Offset * _elementSize, result, TdsEnums.VECTOR_HEADER_SIZE, segment.Count * _elementSize);
+ }
+ else
+ {
+ Buffer.BlockCopy(values.ToArray(), 0, result, TdsEnums.VECTOR_HEADER_SIZE, values.Length * _elementSize);
+ }
+#else
+ // Fast span-based copy.
+ var byteSpan = MemoryMarshal.AsBytes(values.Span);
+ byteSpan.CopyTo(result.AsSpan(TdsEnums.VECTOR_HEADER_SIZE));
+#endif
+ return result;
+ }
+
+ private (int, int) GetCountsOrThrow(byte[] rawBytes)
+ {
+ // Validate some of the header fields.
+ if (
+ // Do we have enough bytes for the header?
+ rawBytes.Length < TdsEnums.VECTOR_HEADER_SIZE ||
+ // Do we have the expected magic number?
+ rawBytes[0] != VecHeaderMagicNo ||
+ // Do we support the version?
+ rawBytes[1] != VecVersionNo ||
+ // Do the vector types match?
+ rawBytes[4] != _elementType)
+ {
+ // No, so throw.
+ throw ADP.InvalidVectorHeader();
+ }
+
+ // The vector length is an unsigned 16-bit integer, little-endian.
+ int length = BinaryPrimitives.ReadUInt16LittleEndian(rawBytes.AsSpan(2));
+
+ // The vector size is the number of bytes required to represent the vector in TDS.
+ int size = TdsEnums.VECTOR_HEADER_SIZE + (_elementSize * length);
+
+ // Are there exactly enough bytes for the vector elements?
+ if (rawBytes.Length != size)
+ {
+ // No, so throw.
+ throw ADP.InvalidVectorHeader();
+ }
+
+ return (length, size);
+ }
+
+ private T[] MakeArray()
+ {
+ if (_tdsBytes.Length == 0)
+ {
+ return Array.Empty();
+ }
+
+#if NETFRAMEWORK
+ // Allocate array and copy bytes into it
+ T[] result = new T[Length];
+ Buffer.BlockCopy(_tdsBytes, 8, result, 0, _elementSize * Length);
+ return result;
+#else
+ ReadOnlySpan dataSpan = _tdsBytes.AsSpan(8, _elementSize * Length);
+ return MemoryMarshal.Cast(dataSpan).ToArray();
+#endif
+ }
+
+ #endregion
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVectorFloat32.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVectorFloat32.cs
deleted file mode 100644
index 895cb2f07a..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVectorFloat32.cs
+++ /dev/null
@@ -1,179 +0,0 @@
-using System;
-using System.Buffers.Binary;
-using System.Data.SqlTypes;
-using System.Text.Json;
-using Microsoft.Data.Common;
-using Microsoft.Data.SqlClient;
-
-#nullable enable
-namespace Microsoft.Data.SqlTypes
-{
- ///
- public sealed class SqlVectorFloat32 : INullable, ISqlVector
- {
- #region Constants
- private const byte VecHeaderMagicNo = 0xA9;
- private const byte VecVersionNo = 0x01;
- private const byte VecTypeFloat32 = 0x00;
-
- #endregion
-
- #region Fields
- private readonly byte _elementSize = sizeof(float);
- private readonly int _elementCount;
- private readonly byte[] _rawBytes;
- private readonly byte _elementType = VecTypeFloat32;
- #endregion
-
- #region Constructors
- private SqlVectorFloat32()
- {
- _elementCount = 0;
- _rawBytes = Array.Empty();
- }
-
- internal SqlVectorFloat32(byte[] rawBytes)
- {
- if (!ValidateRawBytes(rawBytes))
- {
- throw ADP.InvalidVectorHeader();
- }
- _rawBytes = rawBytes;
- _elementCount = BinaryPrimitives.ReadUInt16LittleEndian(rawBytes.AsSpan(2));
- var floatArray = new float[_elementCount];
- Buffer.BlockCopy(_rawBytes, 8, floatArray, 0, _elementCount * _elementSize);
- Values = new ReadOnlyMemory(floatArray);
- }
-
- ///
- public SqlVectorFloat32(int length)
- {
- if (length < 0)
- throw ADP.InvalidVectorColumnLength(nameof(length));
-
- _elementCount = length;
- _rawBytes = Array.Empty();
- Values = new ReadOnlyMemory(Array.Empty());
- }
-
- ///
- public SqlVectorFloat32(ReadOnlyMemory values)
- {
- Values = values;
- _elementCount = values.Length;
- _rawBytes = new byte[TdsEnums.VECTOR_HEADER_SIZE + _elementCount * _elementSize];
- InitializeVectorBytes(values);
- }
- #endregion
-
- #region Methods
- ///
- public override string ToString()
- {
- if (IsNull || _rawBytes == null)
- {
- return SQLResource.NullString;
- }
- return JsonSerializer.Serialize(this.Values);
- }
-
- ///
- public float[] ToArray()
- {
- if (IsNull || _rawBytes == null)
- {
- return Array.Empty();
- }
- return Values.ToArray();
- }
- #endregion
-
- #region Properties
- ///
- public bool IsNull => _rawBytes.Length == 0;
-
- ///
- public static SqlVectorFloat32? Null => null;
-
- ///
- public int Length => _elementCount;
- ///
- public int Size => TdsEnums.VECTOR_HEADER_SIZE + _elementCount * _elementSize;
-
- ///
- public ReadOnlyMemory Values { get; }
- #endregion
-
- #region ISqlVectorProperties
- byte ISqlVector.ElementType => _elementType;
- byte ISqlVector.ElementSize => _elementSize;
- byte[] ISqlVector.VectorPayload => _rawBytes;
- #endregion
-
- #region Helpers
- private void InitializeVectorBytes(ReadOnlyMemory values)
- {
- //Refer to TDS section 2.2.5.5.7 for vector header format
- // +------------------------+-----------------+----------------------+------------------+----------------------------+--------------+
- // | Field | Size (bytes) | Example Value | Description |
- // +------------------------+-----------------+----------------------+--------------------------------------------------------------+
- // | Layout Format | 1 | 0xA9 | Magic number indicating vector layout format |
- // | Layout Version | 1 | 0x01 | Version of the vector format |
- // | Number of Dimensions | 2 | NN | Number of vector elements |
- // | Dimension Type | 1 | 0x00 | Element type indicator (e.g. 0x00 for float32) |
- // | Reserved | 3 | 0x00 0x00 0x00 | Reserved for future use |
- // | Stream of Values | NN * sizeof(T) | [float bytes...] | Raw bytes for vector elements |
- // +------------------------+-----------------+----------------------+--------------------------------------------------------------+
-
- _rawBytes[0] = VecHeaderMagicNo;
- _rawBytes[1] = VecVersionNo;
- BinaryPrimitives.WriteUInt16LittleEndian(_rawBytes.AsSpan(2), (ushort)_elementCount);
- _rawBytes[4] = VecTypeFloat32;
- _rawBytes[5] = 0x00;
- _rawBytes[6] = 0x00;
- _rawBytes[7] = 0x00;
-
-
- // Write float data in little-endian format
- Span dest = _rawBytes.AsSpan(TdsEnums.VECTOR_HEADER_SIZE);
-
-#if NETFRAMEWORK
- // .NET Framework: Use BitConverter with endianness check
- ReadOnlySpan floatSpan = values.Span;
- for (int i = 0; i < floatSpan.Length; i++)
- {
- byte[] bytes = BitConverter.GetBytes(floatSpan[i]);
- if (!BitConverter.IsLittleEndian)
- {
- Array.Reverse(bytes);
- }
- bytes.CopyTo(dest.Slice(i * sizeof(float)));
- }
-#else
- // .NET 8.0+: Use BinaryPrimitives for high-performance little-endian writing
- ReadOnlySpan floatSpan = values.Span;
- for (int i = 0; i < floatSpan.Length; i++)
- {
- BinaryPrimitives.WriteSingleLittleEndian(dest.Slice(i * sizeof(float)), floatSpan[i]);
- }
-#endif
- }
-
- private bool ValidateRawBytes(byte[] rawBytes)
- {
- if (rawBytes.Length == 0 || rawBytes.Length < TdsEnums.VECTOR_HEADER_SIZE)
- return false;
-
- if (rawBytes[0] != VecHeaderMagicNo || rawBytes[1] != VecVersionNo || rawBytes[4] != VecTypeFloat32)
- return false;
-
- int elementCount = BinaryPrimitives.ReadUInt16LittleEndian(rawBytes.AsSpan(2));
- if (rawBytes.Length != TdsEnums.VECTOR_HEADER_SIZE + elementCount * _elementSize)
- return false;
-
- return true;
- }
-
- #endregion
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
index 87bd19f054..b41b331db7 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
@@ -10862,28 +10862,6 @@ internal static string ADP_InvalidVectorHeader
}
}
- ///
- /// Looks up a localized string similar to Vector column length must be non-negative..
- ///
- internal static string ADP_InvalidVectorColumnLength
- {
- get
- {
- return ResourceManager.GetString("ADP_InvalidVectorColumnLength", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Values {0} cannot be null or empty..
- ///
- internal static string ADP_EmptyVectorValues
- {
- get
- {
- return ResourceManager.GetString("ADP_EmptyVectorValues", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to {0} Invalid JSON string for vector...
///
@@ -10895,7 +10873,6 @@ internal static string ADP_InvalidJsonStringForVector
}
}
-
///
/// Looks up a localized string similar to Expecting argument of type {1}, but received type {0}..
///
diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
index f508014a82..90a157876a 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
@@ -4755,12 +4755,6 @@
Invalid vector header received.
-
- Vector column length must be non-negative.
-
-
- Values cannot be null or empty.
-
{0} Invalid JSON string for vector.
diff --git a/src/Microsoft.Data.SqlClient/src/System/Runtime/CompilerServices/IsExternalInit.netfx.cs b/src/Microsoft.Data.SqlClient/src/System/Runtime/CompilerServices/IsExternalInit.netfx.cs
new file mode 100644
index 0000000000..0d0181ba6d
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/System/Runtime/CompilerServices/IsExternalInit.netfx.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if NETFRAMEWORK
+
+using System.ComponentModel;
+
+
+// This class enables the use of the `init` property accessor in .NET framework.
+namespace System.Runtime.CompilerServices
+{
+ ///
+ /// Reserved to be used by the compiler for tracking metadata.
+ /// This class should not be used by developers in source code.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal static class IsExternalInit
+ {
+ }
+}
+
+#endif
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj
index 3ab6fed746..56265208b4 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj
@@ -65,7 +65,6 @@
-
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlVectorFloat32Test.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlVectorFloat32Test.cs
deleted file mode 100644
index 0bd08340d7..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlVectorFloat32Test.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-using System;
-using Microsoft.Data.SqlTypes;
-using Xunit;
-
-namespace Microsoft.Data.SqlClient.Tests
-{
- public class SqlVectorFloat32Test
- {
- [Fact]
- public void Constructor_WithValidLength_ShouldSetLength()
- {
- var vec = new SqlVectorFloat32(5);
- Assert.Equal(5, vec.Length);
- Assert.True(vec.IsNull);
- }
-
- [Fact]
- public void Constructor_WithNegativeLength_ShouldThrow()
- {
- Assert.Throws(() => new SqlVectorFloat32(-1));
- }
-
- [Fact]
- public void Constructor_WithEmptyValues_ShouldNotThrow()
- {
- var vec = new SqlVectorFloat32(values: Array.Empty());
- Assert.Equal(0, vec.Length);
- Assert.False(vec.IsNull);
- }
-
- [Fact]
- public void Constructor_WithValues_ShouldSetProperties()
- {
- float[] data = new float[] { 1.1f, 2.2f };
- var vec = new SqlVectorFloat32(values: data);
- Assert.Equal(2, vec.Length);
- Assert.False(vec.IsNull);
- Assert.Equal(data, vec.Values.ToArray());
- }
-
- [Fact]
- public void Constructor_WithReadOnlyMem_ShouldSetProperties()
- {
- ReadOnlyMemory data = new float[] { 1.1f, 2.2f, 3.3f };
- var vec = new SqlVectorFloat32(values: data);
- Assert.Equal(3, vec.Length);
- Assert.False(vec.IsNull);
- Assert.Equal(data.ToArray(), vec.Values.ToArray());
- }
-
- [Fact]
- public void ToString_ShouldReturnJsonString()
- {
- float[] data = new float[] { 3.14f };
- var vec = new SqlVectorFloat32(values: data);
- string json = vec.ToString();
- Assert.Contains("3.14", json);
- }
-
- [Fact]
- public void ToString_ShouldReturnNullString()
- {
- var vec = new SqlVectorFloat32(0);
- string nullStr = vec.ToString();
- Assert.Contains("Null", nullStr);
- }
-
- [Fact]
- public void IsNull_WithLengthCtor_ShouldBeTrue()
- {
- var vec = new SqlVectorFloat32(0);
- Assert.True(vec.IsNull);
- }
-
- [Fact]
- public void Null_Property_ShouldReturnNull()
- {
- SqlVectorFloat32 vec = SqlVectorFloat32.Null;
- Assert.Null(vec);
- }
-
- [Fact]
- public void Values_WhenNull_ShouldReturnEmptyArray()
- {
- var vec = new SqlVectorFloat32(0);
- Assert.Empty(vec.Values.ToArray());
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs
index 158bb52aca..8d205cfc9c 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs
@@ -1,3 +1,7 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
using System;
using System.Collections.Generic;
using System.Data;
@@ -18,33 +22,33 @@ public static class VectorFloat32TestData
public static int vectorColumnLength = testData.Length;
public static IEnumerable