Skip to content

GetFieldType() does not Return SqlVector<float> #4104

Closed

Description

@rhuijben

Describe the bug

SqlDataReader.GetFieldType() does not properly identify a VECTOR(3) field as vector, while .GetValue() and others
do the proper identification.

To reproduce

Based on original Vector usage sample this code triggers the issue with a set of assertions

class Vectors
{
    public int Id { get; set; }
    public SqlVector<float> VectorData { get; set; }
}

using var connection = new SqlConnection("...");
connection.Open();

var vectorDimensionCount = 3;

using (var command = connection.CreateCommand())
{
    command.CommandText = $@" IF OBJECT_ID('{nameof(Vectors)}', 'U') IS NOT NULL DROP TABLE {nameof(Vectors)};";
    command.ExecuteNonQuery();
}

using (var command = connection.CreateCommand())
{
    command.CommandText = $@"
        CREATE TABLE {nameof(Vectors)} (
            Id INT IDENTITY(1,1) PRIMARY KEY,
            VectorData VECTOR({vectorDimensionCount})
        );";
    command.ExecuteNonQuery();
}

// Raw insert, from Microsoft sample code
using (var command = connection.CreateCommand())
{
    command.CommandText = $@"INSERT INTO {nameof(Vectors)} (VectorData) VALUES (@VectorData)";
    var param = command.Parameters.Add("@VectorData", SqlDbTypeExtensions.Vector);

    // Insert non-null vector
    param.Value = new SqlVector<float>(new float[] { 3.14159f, 1.61803f, 1.41421f });
    command.ExecuteNonQuery();
}

using (var command = connection.CreateCommand())
{
    command.CommandText = $@"SELECT Id, VectorData FROM {nameof(Vectors)}";
    using var reader = command.ExecuteReader();

    Assert.AreEqual(typeof(int), reader.GetFieldType(0));
    // Assert.AreEqual(typeof(SqlVector<float>), reader.GetFieldType(1)); // This should be the check
    Assert.AreEqual(typeof(byte[]), reader.GetFieldType(1)); // This should return SqlVector<float>, but currently returns byte[] due to a bug in SqlClient

    while (reader.Read())
    {
        var value = reader.GetValue(1);

        Assert.IsInstanceOfType<SqlVector<float>>(value); // This succeeds, so the library is correctly identifying the type as SqlVector<float> and returning it as such, but the GetFieldType method is incorrectly reporting it as byte[] due to a bug in SqlClient

        var id = reader.GetFieldValue<int>(0);
        var dt = reader.GetFieldValue<SqlVector<float>>(1);

        Assert.ThrowsExactly<InvalidCastException>(() => reader.GetFieldValue<byte[]>(1));


        var dt2 = reader.GetSqlVector<float>(1);
    }
}

This sample passes with 7.0.0 on SqlServer 2025

Expected behavior

The GetFieldType call returns a type with which the value is not retrievable. It should return the right type, or at least a type with which the value is retrievable. (Returning typeof(object) is better than typeof(byte[]).

Given that the .GetValue(1) does the right thing, I think it should just return typeof(SqlVector<float>)

Further technical details

Tested with SqlServer 2025 (linux docker) on .Net 10 (=RepoDB test environment), but it looks like it is reproducable on many more configurations

Activity

  1. added
    Area\VectorUse this for issues that are targeted for the Vector feature in the driver.
    on Mar 31, 2026
  2. moved this from To triage to In progress in SqlClient Boardon Mar 31, 2026
  3. added this to the 7.0.1 milestone on Mar 31, 2026
  4. moved this from In progress to In review in SqlClient Boardon Mar 31, 2026
  5. rhuijben commented on Mar 31, 2026

    @rhuijben
    Author

    A fix would probably have changes like this...

    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 dc1abf1c..04683c3c 100644
    --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs
    +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs
    @@ -1136,8 +1136,16 @@ namespace Microsoft.Data.SqlClient
                         case StorageType.String:
                             return String;
                         case StorageType.SqlBinary:
    -                    case StorageType.Vector:
                             return ByteArray;
    +                    case StorageType.Vector:
    +                        var elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType;
    +                        switch (elementType)
    +                        {
    +                            case MetaType.SqlVectorElementType.Float32:
    +                                return GetSqlVector<float>();
    +                            default:
    +                                throw SQL.VectorTypeNotSupported(elementType.ToString());
    +                        }
                         case StorageType.SqlCachedBuffer:
                             {
                                 // If we have a CachedBuffer, it's because it's an XMLTYPE column
    @@ -1213,6 +1221,15 @@ namespace Microsoft.Data.SqlClient
                         case StorageType.Json:
                             return typeof(SqlJson);
                             // Time Date DateTime2 and DateTimeOffset have no direct Sql type to contain them
    +                    case StorageType.Vector:
    +                        var elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType;
    +                        switch (elementType)
    +                        {
    +                            case MetaType.SqlVectorElementType.Float32:
    +                                return typeof(SqlVector<float>);
    +                            default:
    +                                throw SQL.VectorTypeNotSupported(elementType.ToString());
    +                        }
                     }
                 }
                 else
    @@ -1262,7 +1279,14 @@ namespace Microsoft.Data.SqlClient
                         case StorageType.Json:
                             return typeof(string);
                         case StorageType.Vector:
    -                        return typeof(byte[]);
    +                        var elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType;
    +                        switch (elementType)
    +                        {
    +                            case MetaType.SqlVectorElementType.Float32:
    +                                return typeof(SqlVector<float>);
    +                            default:
    +                                throw SQL.VectorTypeNotSupported(elementType.ToString());
    +                        }
     #if NET
                         case StorageType.Time:
                             return typeof(TimeOnly);

    But I'm not familiar enough with the code to see how I can avoid all the code duplication around this. (And I think with SqlServer 2025 more types than just float32 should be supported)

  6. added and removed
    Hotfix Candidate 🚑Issues/PRs that are candidate for backporting to earlier supported versions.
    on Apr 1, 2026
  7. moved this from In review to Done in SqlClient Boardon Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Area\VectorUse this for issues that are targeted for the Vector feature in the driver.

Type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions