From 12cf0b11ef4d24fd76db563790a417722afc9589 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:27:43 +0100 Subject: [PATCH 01/26] Move all vector tests to NativeVectorTestsBase.cs --- ...icrosoft.Data.SqlClient.ManualTests.csproj | 1 + .../VectorTest/NativeVectorFloat32Tests.cs | 623 +---------------- .../SQL/VectorTest/NativeVectorTestsBase.cs | 626 ++++++++++++++++++ 3 files changed, 629 insertions(+), 621 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 83dd4a8351..9ce2c7c0d5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -243,6 +243,7 @@ + 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 0a86534112..6911c37492 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -2,625 +2,6 @@ // 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; -using System.Data.SqlTypes; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.Data.SqlTypes; -using Xunit; -using Xunit.Abstractions; +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest -{ - public static class VectorFloat32TestData - { - public const int VectorHeaderSize = 8; - public static float[] testData = new float[] { 1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f }; - public static int vectorColumnLength = testData.Length; - // Incorrect size for SqlParameter.Size - public static int IncorrectParamSize = 3234; - public static IEnumerable GetVectorFloat32TestData() - { - // Pattern 1-4 with SqlVector(values: testData) - yield return new object[] { 1, new SqlVector(testData), testData, vectorColumnLength }; - yield return new object[] { 2, new SqlVector(testData), testData, vectorColumnLength }; - yield return new object[] { 3, new SqlVector(testData), testData, vectorColumnLength }; - yield return new object[] { 4, new SqlVector(testData), testData, vectorColumnLength }; - - // Pattern 1-4 with SqlVector(n) - yield return new object[] { 1, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; - yield return new object[] { 2, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; - yield return new object[] { 3, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; - yield return new object[] { 4, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; - - // Pattern 1-4 with DBNull - yield return new object[] { 1, DBNull.Value, Array.Empty(), vectorColumnLength }; - yield return new object[] { 2, DBNull.Value, Array.Empty(), vectorColumnLength }; - yield return new object[] { 3, DBNull.Value, Array.Empty(), vectorColumnLength }; - yield return new object[] { 4, DBNull.Value, Array.Empty(), vectorColumnLength }; - - // Pattern 1-4 with SqlVector.Null - yield return new object[] { 1, SqlVector.Null, Array.Empty(), vectorColumnLength }; - - // Following scenario is not supported in SqlClient. - // This can only be fixed with a behavior change that SqlParameter.Value is internally set to DBNull.Value if it is set to null. - //yield return new object[] { 2, SqlVector.Null, Array.Empty(), vectorColumnLength }; - - yield return new object[] { 3, SqlVector.Null, Array.Empty(), vectorColumnLength }; - yield return new object[] { 4, SqlVector.Null, Array.Empty(), vectorColumnLength }; - } - } - - public sealed class NativeVectorFloat32Tests : IDisposable - { - private readonly ITestOutputHelper _output; - private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; - private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); - private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); - private static readonly int s_vectorDimensions = VectorFloat32TestData.vectorColumnLength; - private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector({s_vectorDimensions}) NULL)"; - private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector({s_vectorDimensions}) NULL)"; - private static readonly string s_selectCmdString = $"SELECT VectorData FROM {s_tableName} ORDER BY Id DESC"; - private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} (VectorData) VALUES (@VectorData)"; - private static readonly string s_vectorParamName = $"@VectorData"; - private static readonly string s_outputVectorParamName = $"@OutputVectorData"; - private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); - private static readonly string s_storedProcBody = $@" - {s_vectorParamName} vector({s_vectorDimensions}), -- Input: Serialized float[] as JSON string - {s_outputVectorParamName} vector({s_vectorDimensions}) OUTPUT -- Output: Echoed back from latest inserted row - AS - BEGIN - SET NOCOUNT ON; - - -- Insert into vector table - INSERT INTO {s_tableName} (VectorData) - VALUES ({s_vectorParamName}); - - -- Retrieve latest entry (assumes auto-incrementing ID) - SELECT TOP 1 {s_outputVectorParamName} = VectorData - FROM {s_tableName} - ORDER BY Id DESC; - END;"; - - public NativeVectorFloat32Tests(ITestOutputHelper output) - { - _output = output; - using var connection = new SqlConnection(s_connectionString); - connection.Open(); - DataTestUtility.CreateTable(connection, s_tableName, s_tableDefinition); - DataTestUtility.CreateTable(connection, s_bulkCopySrcTableName, s_bulkCopySrcTableDef); - DataTestUtility.CreateSP(connection, s_storedProcName, s_storedProcBody); - } - - public void Dispose() - { - using var connection = new SqlConnection(s_connectionString); - connection.Open(); - DataTestUtility.DropTable(connection, s_tableName); - DataTestUtility.DropTable(connection, s_bulkCopySrcTableName); - DataTestUtility.DropStoredProcedure(connection, s_storedProcName); - } - - private void ValidateSqlVectorFloat32Object(bool isNull, SqlVector sqlVectorFloat32, float[] expectedData, int expectedLength) - { - Assert.Equal(expectedData, sqlVectorFloat32.Memory.ToArray()); - Assert.Equal(expectedLength, sqlVectorFloat32.Length); - if (!isNull) - { - Assert.False(sqlVectorFloat32.IsNull, "IsNull set to true for a non-null value"); - } - else - { - Assert.True(sqlVectorFloat32.IsNull, "IsNull set to false for a null value"); - } - } - - private void ValidateInsertedData(SqlConnection connection, float[] expectedData, int expectedLength) - { - using var selectCmd = new SqlCommand(s_selectCmdString, connection); - using var reader = selectCmd.ExecuteReader(); - Assert.True(reader.Read(), "No data found in the table."); - - //For both null and non-null cases, validate the SqlVector object - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); - - if (!reader.IsDBNull(0)) - { - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader["VectorData"], expectedData, expectedLength); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetFieldValue(0))); - } - else - { - Assert.Equal(DBNull.Value, reader.GetValue(0)); - Assert.Equal(DBNull.Value, reader[0]); - Assert.Equal(DBNull.Value, reader["VectorData"]); - Assert.Throws(() => reader.GetString(0)); - Assert.Throws(() => reader.GetSqlString(0).Value); - Assert.Throws(() => reader.GetFieldValue(0)); - } - } - - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] - public void TestSqlVectorFloat32ParameterInsertionAndReads( - int pattern, - object value, - float[] expectedValues, - int expectedLength) - { - using var conn = new SqlConnection(s_connectionString); - conn.Open(); - - using var insertCmd = new SqlCommand(s_insertCmdString, conn); - - SqlParameter param = pattern switch - { - 1 => new SqlParameter - { - ParameterName = s_vectorParamName, - SqlDbType = SqlDbTypeExtensions.Vector, - Value = value - }, - 2 => new SqlParameter(s_vectorParamName, value), - 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, - // Even if size is specified, the actual size is determined by the value passed and specified size is ignored. - 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, - _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") - }; - - insertCmd.Parameters.Add(param); - Assert.Equal(1, insertCmd.ExecuteNonQuery()); - insertCmd.Parameters.Clear(); - - ValidateInsertedData(conn, expectedValues, expectedLength); - } - - private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] expectedData, int expectedLength) - { - using var selectCmd = new SqlCommand(s_selectCmdString, connection); - using var reader = await selectCmd.ExecuteReaderAsync(); - Assert.True(await reader.ReadAsync(), "No data found in the table."); - - //For both null and non-null cases, validate the SqlVector object - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); - - if (!await reader.IsDBNullAsync(0)) - { - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader["VectorData"], expectedData, expectedLength); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); - Assert.Equal(expectedData, JsonSerializer.Deserialize(await reader.GetFieldValueAsync(0))); - } - else - { - Assert.Equal(DBNull.Value, reader.GetValue(0)); - Assert.Equal(DBNull.Value, reader[0]); - Assert.Equal(DBNull.Value, reader["VectorData"]); - Assert.Throws(() => reader.GetString(0)); - Assert.Throws(() => reader.GetSqlString(0).Value); - await Assert.ThrowsAsync(async () => await reader.GetFieldValueAsync(0)); - } - } - - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] - public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( - int pattern, - object value, - float[] expectedValues, - int expectedLength) - { - using var conn = new SqlConnection(s_connectionString); - await conn.OpenAsync(); - - using var insertCmd = new SqlCommand(s_insertCmdString, conn); - - SqlParameter param = pattern switch - { - 1 => new SqlParameter - { - ParameterName = s_vectorParamName, - SqlDbType = (SqlDbType)36, // SqlDbTypeExtension.Vector - Value = value - }, - 2 => new SqlParameter(s_vectorParamName, value), - 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, - _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") - }; - - insertCmd.Parameters.Add(param); - Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); - insertCmd.Parameters.Clear(); - - await ValidateInsertedDataAsync(conn, expectedValues, expectedLength); - } - - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] - public void TestStoredProcParamsForVectorFloat32( - int pattern, - object value, - float[] expectedValues, - int expectedLength) - { - //Create SP for test - using var conn = new SqlConnection(s_connectionString); - conn.Open(); - DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); - using var command = new SqlCommand(s_storedProcName, conn) - { - CommandType = CommandType.StoredProcedure - }; - - // Set input and output parameters - SqlParameter inputParam = pattern switch - { - 1 => new SqlParameter - { - ParameterName = s_vectorParamName, - SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector - Value = value - }, - 2 => new SqlParameter(s_vectorParamName, value), - 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, - _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") - }; - command.Parameters.Add(inputParam); - - var outputParam = new SqlParameter - { - ParameterName = s_outputVectorParamName, - SqlDbType = SqlDbTypeExtensions.Vector, - Direction = ParameterDirection.Output, - Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) - }; - command.Parameters.Add(outputParam); - - // Execute the stored procedure - command.ExecuteNonQuery(); - - // Validate the output parameter - var vector = (SqlVector)outputParam.Value; - ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); - - // Validate error for conventional way of setting output parameters - command.Parameters.Clear(); - command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; - command.Parameters.Add(outputParamWithoutVal); - Assert.Throws(() => command.ExecuteNonQuery()); - } - - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] - public async Task TestStoredProcParamsForVectorFloat32Async( - int pattern, - object value, - float[] expectedValues, - int expectedLength) - { - //Create SP for test - using var conn = new SqlConnection(s_connectionString); - await conn.OpenAsync(); - DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); - using var command = new SqlCommand(s_storedProcName, conn) - { - CommandType = CommandType.StoredProcedure - }; - - // Set input and output parameters - SqlParameter inputParam = pattern switch - { - 1 => new SqlParameter - { - ParameterName = s_vectorParamName, - SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector - Value = value - }, - 2 => new SqlParameter(s_vectorParamName, value), - 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, - _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") - }; - command.Parameters.Add(inputParam); - - var outputParam = new SqlParameter - { - ParameterName = s_outputVectorParamName, - SqlDbType = SqlDbTypeExtensions.Vector, - Direction = ParameterDirection.Output, - Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) - }; - command.Parameters.Add(outputParam); - - // Execute the stored procedure - await command.ExecuteNonQueryAsync(); - - // Validate the output parameter - var vector = (SqlVector)outputParam.Value; - ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); - - // Validate error for conventional way of setting output parameters - command.Parameters.Clear(); - command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; - command.Parameters.Add(outputParamWithoutVal); - await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync()); - } - - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [InlineData(1)] - [InlineData(2)] - public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) - { - //Setup source with test data and create destination table for bulkcopy. - SqlConnection sourceConnection = new SqlConnection(s_connectionString); - sourceConnection.Open(); - SqlConnection destinationConnection = new SqlConnection(s_connectionString); - destinationConnection.Open(); - DataTable table = null; - switch (bulkCopySourceMode) - { - - case 1: - // Use SqlServer table as source - var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); - var vectorParam = new SqlParameter(s_vectorParamName, new SqlVector(VectorFloat32TestData.testData)); - - // Insert 2 rows with one non-null and null value - insertCmd.Parameters.Add(vectorParam); - Assert.Equal(1, insertCmd.ExecuteNonQuery()); - insertCmd.Parameters.Clear(); - vectorParam.Value = DBNull.Value; - insertCmd.Parameters.Add(vectorParam); - Assert.Equal(1, insertCmd.ExecuteNonQuery()); - insertCmd.Parameters.Clear(); - break; - case 2: - table = new DataTable(s_bulkCopySrcTableName); - table.Columns.Add("Id", typeof(int)); - table.Columns.Add("VectorData", typeof(SqlVector)); - table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); - table.Rows.Add(2, DBNull.Value); - break; - default: - throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); - } - - - - //Bulkcopy from sql server table to destination table - using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); - using SqlDataReader reader = sourceDataCommand.ExecuteReader(); - - // Verify that the destination table is empty before bulk copy - using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); - Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); - - // Initialize bulk copy configuration - using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) - { - DestinationTableName = s_tableName, - }; - - try - { - switch (bulkCopySourceMode) - { - case 1: - bulkCopy.WriteToServer(reader); - break; - case 2: - bulkCopy.WriteToServer(table); - break; - default: - throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); - } - } - catch (Exception ex) - { - // If bulk copy fails, fail the test with the exception message - Assert.Fail($"Bulk copy failed: {ex.Message}"); - } - - // Verify that the 2 rows from the source table have been copied into the destination table. - Assert.Equal(2, Convert.ToInt16(countCommand.ExecuteScalar())); - - // Read the data from destination table as varbinary to verify the UTF-8 byte sequence - using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); - using SqlDataReader verifyReader = verifyCommand.ExecuteReader(); - - // Verify that we have data in the destination table - Assert.True(verifyReader.Read(), "No data found in destination table after bulk copy."); - - // Validate first non-null value. - Assert.True(!verifyReader.IsDBNull(0), "First row in the table is null."); - Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); - - // Verify that we have another row - Assert.True(verifyReader.Read(), "Second row not found in the table"); - - // Verify that we have encountered null. - Assert.True(verifyReader.IsDBNull(0)); - Assert.Equal(Array.Empty(), ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); - } - - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [InlineData(1)] - [InlineData(2)] - public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) - { - //Setup source with test data and create destination table for bulkcopy. - SqlConnection sourceConnection = new SqlConnection(s_connectionString); - await sourceConnection.OpenAsync(); - SqlConnection destinationConnection = new SqlConnection(s_connectionString); - await destinationConnection.OpenAsync(); - - DataTable table = null; - switch (bulkCopySourceMode) - { - - case 1: - // Use SqlServer table as source - var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); - var vectorParam = new SqlParameter(s_vectorParamName, new SqlVector(VectorFloat32TestData.testData)); - - // Insert 2 rows with one non-null and null value - insertCmd.Parameters.Add(vectorParam); - Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); - insertCmd.Parameters.Clear(); - vectorParam.Value = DBNull.Value; - insertCmd.Parameters.Add(vectorParam); - Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); - insertCmd.Parameters.Clear(); - break; - case 2: - table = new DataTable(s_bulkCopySrcTableName); - table.Columns.Add("Id", typeof(int)); - table.Columns.Add("VectorData", typeof(SqlVector)); - table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); - table.Rows.Add(2, DBNull.Value); - break; - default: - throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); - } - - //Bulkcopy from sql server table to destination table - using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); - using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); - - // Verify that the destination table is empty before bulk copy - using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); - Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); - - // Initialize bulk copy configuration - using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) - { - DestinationTableName = s_tableName, - }; - - try - { // Perform bulkcopy - switch (bulkCopySourceMode) - { - case 1: - await bulkCopy.WriteToServerAsync(reader); - break; - case 2: - await bulkCopy.WriteToServerAsync(table); - break; - default: - throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); - } - } - catch (Exception ex) - { - // If bulk copy fails, fail the test with the exception message - Assert.Fail($"Bulk copy failed: {ex.Message}"); - } - - // Verify that the 2 rows from the source table have been copied into the destination table. - Assert.Equal(2, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); - - // Read the data from destination table as varbinary to verify the UTF-8 byte sequence - using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); - using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(); - - // Verify that we have data in the destination table - Assert.True(await verifyReader.ReadAsync(), "No data found in destination table after bulk copy."); - - // Validate first non-null value. - Assert.True(!await verifyReader.IsDBNullAsync(0), "First row in the table is null."); - var vector = await verifyReader.GetFieldValueAsync>(0); - Assert.Equal(VectorFloat32TestData.testData, vector.Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); - - // Verify that we have another row - Assert.True(await verifyReader.ReadAsync(), "Second row not found in the table"); - - // Verify that we have encountered null. - Assert.True(await verifyReader.IsDBNullAsync(0)); - vector = await verifyReader.GetFieldValueAsync>(0); - Assert.Equal(Array.Empty(), vector.Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() - { - using var connection = new SqlConnection(s_connectionString); - connection.Open(); - - // Insert a row so we can query it - using (var insertCmd = new SqlCommand(s_insertCmdString, connection)) - { - var param = insertCmd.Parameters.Add(s_vectorParamName, SqlDbTypeExtensions.Vector); - param.Value = new SqlVector(VectorFloat32TestData.testData); - insertCmd.ExecuteNonQuery(); - } - - using var selectCmd = new SqlCommand(s_selectCmdString, connection); - using var reader = selectCmd.ExecuteReader(); - - // Verify GetFieldType returns SqlVector for the vector column - Assert.Equal(typeof(SqlVector), reader.GetFieldType(0)); - - // Verify GetProviderSpecificFieldType also returns SqlVector - Assert.Equal(typeof(SqlVector), reader.GetProviderSpecificFieldType(0)); - - // Verify that GetValue returns an instance consistent with GetFieldType - Assert.True(reader.Read(), "No data found in the table."); - object value = reader.GetValue(0); - Assert.IsType>(value); - Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)value).Memory.ToArray()); - - // Verify GetFieldValue> returns the correct typed value - SqlVector typedValue = reader.GetFieldValue>(0); - Assert.IsType>(typedValue); - Assert.Equal(VectorFloat32TestData.testData, typedValue.Memory.ToArray()); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - public void TestInsertVectorsFloat32WithPrepare() - { - SqlConnection conn = new SqlConnection(s_connectionString); - conn.Open(); - SqlCommand command = new SqlCommand(s_insertCmdString, conn); - SqlParameter vectorParam = new SqlParameter("@VectorData", SqlDbTypeExtensions.Vector); - command.Parameters.Add(vectorParam); - command.Prepare(); - for (int i = 0; i < 10; i++) - { - vectorParam.Value = new SqlVector(new float[] { i + 0.1f, i + 0.2f, i + 0.3f, i + 0.4f, i + 0.5f, i + 0.6f }); - command.ExecuteNonQuery(); - } - SqlCommand validateCommand = new SqlCommand($"SELECT VectorData FROM {s_tableName}", conn); - using SqlDataReader reader = validateCommand.ExecuteReader(); - int rowcnt = 0; - while (reader.Read()) - { - float[] expectedData = new float[] { rowcnt + 0.1f, rowcnt + 0.2f, rowcnt + 0.3f, rowcnt + 0.4f, rowcnt + 0.5f, rowcnt + 0.6f }; - float[] dbData = reader.GetSqlVector(0).Memory.ToArray(); - Assert.Equal(expectedData, dbData); - rowcnt++; - } - Assert.Equal(10, rowcnt); - } - } -} +#nullable enable diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs new file mode 100644 index 0000000000..0a86534112 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -0,0 +1,626 @@ +// 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; +using System.Data.SqlTypes; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Data.SqlTypes; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest +{ + public static class VectorFloat32TestData + { + public const int VectorHeaderSize = 8; + public static float[] testData = new float[] { 1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f }; + public static int vectorColumnLength = testData.Length; + // Incorrect size for SqlParameter.Size + public static int IncorrectParamSize = 3234; + public static IEnumerable GetVectorFloat32TestData() + { + // Pattern 1-4 with SqlVector(values: testData) + yield return new object[] { 1, new SqlVector(testData), testData, vectorColumnLength }; + yield return new object[] { 2, new SqlVector(testData), testData, vectorColumnLength }; + yield return new object[] { 3, new SqlVector(testData), testData, vectorColumnLength }; + yield return new object[] { 4, new SqlVector(testData), testData, vectorColumnLength }; + + // Pattern 1-4 with SqlVector(n) + yield return new object[] { 1, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; + yield return new object[] { 2, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; + yield return new object[] { 3, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; + yield return new object[] { 4, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; + + // Pattern 1-4 with DBNull + yield return new object[] { 1, DBNull.Value, Array.Empty(), vectorColumnLength }; + yield return new object[] { 2, DBNull.Value, Array.Empty(), vectorColumnLength }; + yield return new object[] { 3, DBNull.Value, Array.Empty(), vectorColumnLength }; + yield return new object[] { 4, DBNull.Value, Array.Empty(), vectorColumnLength }; + + // Pattern 1-4 with SqlVector.Null + yield return new object[] { 1, SqlVector.Null, Array.Empty(), vectorColumnLength }; + + // Following scenario is not supported in SqlClient. + // This can only be fixed with a behavior change that SqlParameter.Value is internally set to DBNull.Value if it is set to null. + //yield return new object[] { 2, SqlVector.Null, Array.Empty(), vectorColumnLength }; + + yield return new object[] { 3, SqlVector.Null, Array.Empty(), vectorColumnLength }; + yield return new object[] { 4, SqlVector.Null, Array.Empty(), vectorColumnLength }; + } + } + + public sealed class NativeVectorFloat32Tests : IDisposable + { + private readonly ITestOutputHelper _output; + private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; + private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); + private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); + private static readonly int s_vectorDimensions = VectorFloat32TestData.vectorColumnLength; + private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector({s_vectorDimensions}) NULL)"; + private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector({s_vectorDimensions}) NULL)"; + private static readonly string s_selectCmdString = $"SELECT VectorData FROM {s_tableName} ORDER BY Id DESC"; + private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} (VectorData) VALUES (@VectorData)"; + private static readonly string s_vectorParamName = $"@VectorData"; + private static readonly string s_outputVectorParamName = $"@OutputVectorData"; + private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); + private static readonly string s_storedProcBody = $@" + {s_vectorParamName} vector({s_vectorDimensions}), -- Input: Serialized float[] as JSON string + {s_outputVectorParamName} vector({s_vectorDimensions}) OUTPUT -- Output: Echoed back from latest inserted row + AS + BEGIN + SET NOCOUNT ON; + + -- Insert into vector table + INSERT INTO {s_tableName} (VectorData) + VALUES ({s_vectorParamName}); + + -- Retrieve latest entry (assumes auto-incrementing ID) + SELECT TOP 1 {s_outputVectorParamName} = VectorData + FROM {s_tableName} + ORDER BY Id DESC; + END;"; + + public NativeVectorFloat32Tests(ITestOutputHelper output) + { + _output = output; + using var connection = new SqlConnection(s_connectionString); + connection.Open(); + DataTestUtility.CreateTable(connection, s_tableName, s_tableDefinition); + DataTestUtility.CreateTable(connection, s_bulkCopySrcTableName, s_bulkCopySrcTableDef); + DataTestUtility.CreateSP(connection, s_storedProcName, s_storedProcBody); + } + + public void Dispose() + { + using var connection = new SqlConnection(s_connectionString); + connection.Open(); + DataTestUtility.DropTable(connection, s_tableName); + DataTestUtility.DropTable(connection, s_bulkCopySrcTableName); + DataTestUtility.DropStoredProcedure(connection, s_storedProcName); + } + + private void ValidateSqlVectorFloat32Object(bool isNull, SqlVector sqlVectorFloat32, float[] expectedData, int expectedLength) + { + Assert.Equal(expectedData, sqlVectorFloat32.Memory.ToArray()); + Assert.Equal(expectedLength, sqlVectorFloat32.Length); + if (!isNull) + { + Assert.False(sqlVectorFloat32.IsNull, "IsNull set to true for a non-null value"); + } + else + { + Assert.True(sqlVectorFloat32.IsNull, "IsNull set to false for a null value"); + } + } + + private void ValidateInsertedData(SqlConnection connection, float[] expectedData, int expectedLength) + { + using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var reader = selectCmd.ExecuteReader(); + Assert.True(reader.Read(), "No data found in the table."); + + //For both null and non-null cases, validate the SqlVector object + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + + if (!reader.IsDBNull(0)) + { + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader["VectorData"], expectedData, expectedLength); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetFieldValue(0))); + } + else + { + Assert.Equal(DBNull.Value, reader.GetValue(0)); + Assert.Equal(DBNull.Value, reader[0]); + Assert.Equal(DBNull.Value, reader["VectorData"]); + Assert.Throws(() => reader.GetString(0)); + Assert.Throws(() => reader.GetSqlString(0).Value); + Assert.Throws(() => reader.GetFieldValue(0)); + } + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + public void TestSqlVectorFloat32ParameterInsertionAndReads( + int pattern, + object value, + float[] expectedValues, + int expectedLength) + { + using var conn = new SqlConnection(s_connectionString); + conn.Open(); + + using var insertCmd = new SqlCommand(s_insertCmdString, conn); + + SqlParameter param = pattern switch + { + 1 => new SqlParameter + { + ParameterName = s_vectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, + Value = value + }, + 2 => new SqlParameter(s_vectorParamName, value), + 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, + // Even if size is specified, the actual size is determined by the value passed and specified size is ignored. + 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + + insertCmd.Parameters.Add(param); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + + ValidateInsertedData(conn, expectedValues, expectedLength); + } + + private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] expectedData, int expectedLength) + { + using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var reader = await selectCmd.ExecuteReaderAsync(); + Assert.True(await reader.ReadAsync(), "No data found in the table."); + + //For both null and non-null cases, validate the SqlVector object + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + + if (!await reader.IsDBNullAsync(0)) + { + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader["VectorData"], expectedData, expectedLength); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); + Assert.Equal(expectedData, JsonSerializer.Deserialize(await reader.GetFieldValueAsync(0))); + } + else + { + Assert.Equal(DBNull.Value, reader.GetValue(0)); + Assert.Equal(DBNull.Value, reader[0]); + Assert.Equal(DBNull.Value, reader["VectorData"]); + Assert.Throws(() => reader.GetString(0)); + Assert.Throws(() => reader.GetSqlString(0).Value); + await Assert.ThrowsAsync(async () => await reader.GetFieldValueAsync(0)); + } + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( + int pattern, + object value, + float[] expectedValues, + int expectedLength) + { + using var conn = new SqlConnection(s_connectionString); + await conn.OpenAsync(); + + using var insertCmd = new SqlCommand(s_insertCmdString, conn); + + SqlParameter param = pattern switch + { + 1 => new SqlParameter + { + ParameterName = s_vectorParamName, + SqlDbType = (SqlDbType)36, // SqlDbTypeExtension.Vector + Value = value + }, + 2 => new SqlParameter(s_vectorParamName, value), + 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, + 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + + insertCmd.Parameters.Add(param); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + + await ValidateInsertedDataAsync(conn, expectedValues, expectedLength); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + public void TestStoredProcParamsForVectorFloat32( + int pattern, + object value, + float[] expectedValues, + int expectedLength) + { + //Create SP for test + using var conn = new SqlConnection(s_connectionString); + conn.Open(); + DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); + using var command = new SqlCommand(s_storedProcName, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Set input and output parameters + SqlParameter inputParam = pattern switch + { + 1 => new SqlParameter + { + ParameterName = s_vectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector + Value = value + }, + 2 => new SqlParameter(s_vectorParamName, value), + 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, + 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + command.Parameters.Add(inputParam); + + var outputParam = new SqlParameter + { + ParameterName = s_outputVectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, + Direction = ParameterDirection.Output, + Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) + }; + command.Parameters.Add(outputParam); + + // Execute the stored procedure + command.ExecuteNonQuery(); + + // Validate the output parameter + var vector = (SqlVector)outputParam.Value; + ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); + + // Validate error for conventional way of setting output parameters + command.Parameters.Clear(); + command.Parameters.Add(inputParam); + var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; + command.Parameters.Add(outputParamWithoutVal); + Assert.Throws(() => command.ExecuteNonQuery()); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + public async Task TestStoredProcParamsForVectorFloat32Async( + int pattern, + object value, + float[] expectedValues, + int expectedLength) + { + //Create SP for test + using var conn = new SqlConnection(s_connectionString); + await conn.OpenAsync(); + DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); + using var command = new SqlCommand(s_storedProcName, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Set input and output parameters + SqlParameter inputParam = pattern switch + { + 1 => new SqlParameter + { + ParameterName = s_vectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector + Value = value + }, + 2 => new SqlParameter(s_vectorParamName, value), + 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, + 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + command.Parameters.Add(inputParam); + + var outputParam = new SqlParameter + { + ParameterName = s_outputVectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, + Direction = ParameterDirection.Output, + Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) + }; + command.Parameters.Add(outputParam); + + // Execute the stored procedure + await command.ExecuteNonQueryAsync(); + + // Validate the output parameter + var vector = (SqlVector)outputParam.Value; + ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); + + // Validate error for conventional way of setting output parameters + command.Parameters.Clear(); + command.Parameters.Add(inputParam); + var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; + command.Parameters.Add(outputParamWithoutVal); + await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync()); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [InlineData(1)] + [InlineData(2)] + public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) + { + //Setup source with test data and create destination table for bulkcopy. + SqlConnection sourceConnection = new SqlConnection(s_connectionString); + sourceConnection.Open(); + SqlConnection destinationConnection = new SqlConnection(s_connectionString); + destinationConnection.Open(); + DataTable table = null; + switch (bulkCopySourceMode) + { + + case 1: + // Use SqlServer table as source + var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); + var vectorParam = new SqlParameter(s_vectorParamName, new SqlVector(VectorFloat32TestData.testData)); + + // Insert 2 rows with one non-null and null value + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + vectorParam.Value = DBNull.Value; + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + break; + case 2: + table = new DataTable(s_bulkCopySrcTableName); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("VectorData", typeof(SqlVector)); + table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); + table.Rows.Add(2, DBNull.Value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + + + + //Bulkcopy from sql server table to destination table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlDataReader reader = sourceDataCommand.ExecuteReader(); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + DestinationTableName = s_tableName, + }; + + try + { + switch (bulkCopySourceMode) + { + case 1: + bulkCopy.WriteToServer(reader); + break; + case 2: + bulkCopy.WriteToServer(table); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 2 rows from the source table have been copied into the destination table. + Assert.Equal(2, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); + using SqlDataReader verifyReader = verifyCommand.ExecuteReader(); + + // Verify that we have data in the destination table + Assert.True(verifyReader.Read(), "No data found in destination table after bulk copy."); + + // Validate first non-null value. + Assert.True(!verifyReader.IsDBNull(0), "First row in the table is null."); + Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); + + // Verify that we have another row + Assert.True(verifyReader.Read(), "Second row not found in the table"); + + // Verify that we have encountered null. + Assert.True(verifyReader.IsDBNull(0)); + Assert.Equal(Array.Empty(), ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [InlineData(1)] + [InlineData(2)] + public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) + { + //Setup source with test data and create destination table for bulkcopy. + SqlConnection sourceConnection = new SqlConnection(s_connectionString); + await sourceConnection.OpenAsync(); + SqlConnection destinationConnection = new SqlConnection(s_connectionString); + await destinationConnection.OpenAsync(); + + DataTable table = null; + switch (bulkCopySourceMode) + { + + case 1: + // Use SqlServer table as source + var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); + var vectorParam = new SqlParameter(s_vectorParamName, new SqlVector(VectorFloat32TestData.testData)); + + // Insert 2 rows with one non-null and null value + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + vectorParam.Value = DBNull.Value; + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + break; + case 2: + table = new DataTable(s_bulkCopySrcTableName); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("VectorData", typeof(SqlVector)); + table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); + table.Rows.Add(2, DBNull.Value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + + //Bulkcopy from sql server table to destination table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + DestinationTableName = s_tableName, + }; + + try + { // Perform bulkcopy + switch (bulkCopySourceMode) + { + case 1: + await bulkCopy.WriteToServerAsync(reader); + break; + case 2: + await bulkCopy.WriteToServerAsync(table); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 2 rows from the source table have been copied into the destination table. + Assert.Equal(2, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); + using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(); + + // Verify that we have data in the destination table + Assert.True(await verifyReader.ReadAsync(), "No data found in destination table after bulk copy."); + + // Validate first non-null value. + Assert.True(!await verifyReader.IsDBNullAsync(0), "First row in the table is null."); + var vector = await verifyReader.GetFieldValueAsync>(0); + Assert.Equal(VectorFloat32TestData.testData, vector.Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); + + // Verify that we have another row + Assert.True(await verifyReader.ReadAsync(), "Second row not found in the table"); + + // Verify that we have encountered null. + Assert.True(await verifyReader.IsDBNullAsync(0)); + vector = await verifyReader.GetFieldValueAsync>(0); + Assert.Equal(Array.Empty(), vector.Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() + { + using var connection = new SqlConnection(s_connectionString); + connection.Open(); + + // Insert a row so we can query it + using (var insertCmd = new SqlCommand(s_insertCmdString, connection)) + { + var param = insertCmd.Parameters.Add(s_vectorParamName, SqlDbTypeExtensions.Vector); + param.Value = new SqlVector(VectorFloat32TestData.testData); + insertCmd.ExecuteNonQuery(); + } + + using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var reader = selectCmd.ExecuteReader(); + + // Verify GetFieldType returns SqlVector for the vector column + Assert.Equal(typeof(SqlVector), reader.GetFieldType(0)); + + // Verify GetProviderSpecificFieldType also returns SqlVector + Assert.Equal(typeof(SqlVector), reader.GetProviderSpecificFieldType(0)); + + // Verify that GetValue returns an instance consistent with GetFieldType + Assert.True(reader.Read(), "No data found in the table."); + object value = reader.GetValue(0); + Assert.IsType>(value); + Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)value).Memory.ToArray()); + + // Verify GetFieldValue> returns the correct typed value + SqlVector typedValue = reader.GetFieldValue>(0); + Assert.IsType>(typedValue); + Assert.Equal(VectorFloat32TestData.testData, typedValue.Memory.ToArray()); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + public void TestInsertVectorsFloat32WithPrepare() + { + SqlConnection conn = new SqlConnection(s_connectionString); + conn.Open(); + SqlCommand command = new SqlCommand(s_insertCmdString, conn); + SqlParameter vectorParam = new SqlParameter("@VectorData", SqlDbTypeExtensions.Vector); + command.Parameters.Add(vectorParam); + command.Prepare(); + for (int i = 0; i < 10; i++) + { + vectorParam.Value = new SqlVector(new float[] { i + 0.1f, i + 0.2f, i + 0.3f, i + 0.4f, i + 0.5f, i + 0.6f }); + command.ExecuteNonQuery(); + } + SqlCommand validateCommand = new SqlCommand($"SELECT VectorData FROM {s_tableName}", conn); + using SqlDataReader reader = validateCommand.ExecuteReader(); + int rowcnt = 0; + while (reader.Read()) + { + float[] expectedData = new float[] { rowcnt + 0.1f, rowcnt + 0.2f, rowcnt + 0.3f, rowcnt + 0.4f, rowcnt + 0.5f, rowcnt + 0.6f }; + float[] dbData = reader.GetSqlVector(0).Memory.ToArray(); + Assert.Equal(expectedData, dbData); + rowcnt++; + } + Assert.Equal(10, rowcnt); + } + } +} From 7ec73b7b0a9edd73f120c7f2ff7661b4a9701865 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:36:12 +0100 Subject: [PATCH 02/26] Refactor to enable base class for float32 vector tests --- .../SQL/VectorTest/NativeVectorFloat32Tests.cs | 8 ++++++++ .../SQL/VectorTest/NativeVectorTestsBase.cs | 11 ++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) 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 6911c37492..ce5ff05732 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -5,3 +5,11 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest; #nullable enable + +public sealed class VectorFloat32TestData : VectorTestDataBase +{ +} + +public sealed class NativeVectorFloat32Tests : NativeVectorTestsBase +{ +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 0a86534112..be55c939fc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -14,7 +14,8 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest { - public static class VectorFloat32TestData + public abstract class VectorTestDataBase + where TElement : unmanaged { public const int VectorHeaderSize = 8; public static float[] testData = new float[] { 1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f }; @@ -53,9 +54,10 @@ public static IEnumerable GetVectorFloat32TestData() } } - public sealed class NativeVectorFloat32Tests : IDisposable + public abstract class NativeVectorTestsBase : IDisposable + where TElement : unmanaged + where TTestData : VectorTestDataBase { - private readonly ITestOutputHelper _output; private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); @@ -84,9 +86,8 @@ public sealed class NativeVectorFloat32Tests : IDisposable ORDER BY Id DESC; END;"; - public NativeVectorFloat32Tests(ITestOutputHelper output) + public NativeVectorTestsBase() { - _output = output; using var connection = new SqlConnection(s_connectionString); connection.Open(); DataTestUtility.CreateTable(connection, s_tableName, s_tableDefinition); From 5e46b47dd743157640c91584c63d0ea03611eea8 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:45:51 +0100 Subject: [PATCH 03/26] Lift constant variables and use these consistently --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 98 ++++++++++--------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index be55c939fc..a89759a80a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -58,30 +58,32 @@ public abstract class NativeVectorTestsBase : IDisposable where TElement : unmanaged where TTestData : VectorTestDataBase { + private const string VectorColumnName = "VectorData"; + private const string VectorParameterName = "@VectorData"; + private const string VectorOutputParameterName = "@OutputVectorData"; + private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); private static readonly int s_vectorDimensions = VectorFloat32TestData.vectorColumnLength; - private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector({s_vectorDimensions}) NULL)"; - private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector({s_vectorDimensions}) NULL)"; - private static readonly string s_selectCmdString = $"SELECT VectorData FROM {s_tableName} ORDER BY Id DESC"; - private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} (VectorData) VALUES (@VectorData)"; - private static readonly string s_vectorParamName = $"@VectorData"; - private static readonly string s_outputVectorParamName = $"@OutputVectorData"; + private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}) NULL)"; + private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}) NULL)"; + private static readonly string s_selectCmdString = $"SELECT {VectorColumnName} FROM {s_tableName} ORDER BY Id DESC"; + private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} ({VectorColumnName}) VALUES ({VectorParameterName})"; private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); private static readonly string s_storedProcBody = $@" - {s_vectorParamName} vector({s_vectorDimensions}), -- Input: Serialized float[] as JSON string - {s_outputVectorParamName} vector({s_vectorDimensions}) OUTPUT -- Output: Echoed back from latest inserted row + {VectorParameterName} vector({s_vectorDimensions}), -- Input: Serialized float[] as JSON string + {VectorOutputParameterName} vector({s_vectorDimensions}) OUTPUT -- Output: Echoed back from latest inserted row AS BEGIN SET NOCOUNT ON; -- Insert into vector table - INSERT INTO {s_tableName} (VectorData) - VALUES ({s_vectorParamName}); + INSERT INTO {s_tableName} ({VectorColumnName}) + VALUES ({VectorParameterName}); -- Retrieve latest entry (assumes auto-incrementing ID) - SELECT TOP 1 {s_outputVectorParamName} = VectorData + SELECT TOP 1 {VectorOutputParameterName} = {VectorColumnName} FROM {s_tableName} ORDER BY Id DESC; END;"; @@ -133,7 +135,7 @@ private void ValidateInsertedData(SqlConnection connection, float[] expectedData { ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader["VectorData"], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetFieldValue(0))); @@ -142,7 +144,7 @@ private void ValidateInsertedData(SqlConnection connection, float[] expectedData { Assert.Equal(DBNull.Value, reader.GetValue(0)); Assert.Equal(DBNull.Value, reader[0]); - Assert.Equal(DBNull.Value, reader["VectorData"]); + Assert.Equal(DBNull.Value, reader[VectorColumnName]); Assert.Throws(() => reader.GetString(0)); Assert.Throws(() => reader.GetSqlString(0).Value); Assert.Throws(() => reader.GetFieldValue(0)); @@ -166,14 +168,14 @@ public void TestSqlVectorFloat32ParameterInsertionAndReads( { 1 => new SqlParameter { - ParameterName = s_vectorParamName, + ParameterName = VectorParameterName, SqlDbType = SqlDbTypeExtensions.Vector, Value = value }, - 2 => new SqlParameter(s_vectorParamName, value), - 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, + 2 => new SqlParameter(VectorParameterName, value), + 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, // Even if size is specified, the actual size is determined by the value passed and specified size is ignored. - 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") }; @@ -199,7 +201,7 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] e { ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader["VectorData"], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); Assert.Equal(expectedData, JsonSerializer.Deserialize(await reader.GetFieldValueAsync(0))); @@ -208,7 +210,7 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] e { Assert.Equal(DBNull.Value, reader.GetValue(0)); Assert.Equal(DBNull.Value, reader[0]); - Assert.Equal(DBNull.Value, reader["VectorData"]); + Assert.Equal(DBNull.Value, reader[VectorColumnName]); Assert.Throws(() => reader.GetString(0)); Assert.Throws(() => reader.GetSqlString(0).Value); await Assert.ThrowsAsync(async () => await reader.GetFieldValueAsync(0)); @@ -232,13 +234,13 @@ public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( { 1 => new SqlParameter { - ParameterName = s_vectorParamName, + ParameterName = VectorParameterName, SqlDbType = (SqlDbType)36, // SqlDbTypeExtension.Vector Value = value }, - 2 => new SqlParameter(s_vectorParamName, value), - 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + 2 => new SqlParameter(VectorParameterName, value), + 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, + 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") }; @@ -271,20 +273,20 @@ public void TestStoredProcParamsForVectorFloat32( { 1 => new SqlParameter { - ParameterName = s_vectorParamName, + ParameterName = VectorParameterName, SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector Value = value }, - 2 => new SqlParameter(s_vectorParamName, value), - 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + 2 => new SqlParameter(VectorParameterName, value), + 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, + 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") }; command.Parameters.Add(inputParam); var outputParam = new SqlParameter { - ParameterName = s_outputVectorParamName, + ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, Direction = ParameterDirection.Output, Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) @@ -301,7 +303,7 @@ public void TestStoredProcParamsForVectorFloat32( // Validate error for conventional way of setting output parameters command.Parameters.Clear(); command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; + var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; command.Parameters.Add(outputParamWithoutVal); Assert.Throws(() => command.ExecuteNonQuery()); } @@ -328,20 +330,20 @@ public async Task TestStoredProcParamsForVectorFloat32Async( { 1 => new SqlParameter { - ParameterName = s_vectorParamName, + ParameterName = VectorParameterName, SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector Value = value }, - 2 => new SqlParameter(s_vectorParamName, value), - 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + 2 => new SqlParameter(VectorParameterName, value), + 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, + 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") }; command.Parameters.Add(inputParam); var outputParam = new SqlParameter { - ParameterName = s_outputVectorParamName, + ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, Direction = ParameterDirection.Output, Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) @@ -358,7 +360,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( // Validate error for conventional way of setting output parameters command.Parameters.Clear(); command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; + var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; command.Parameters.Add(outputParamWithoutVal); await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync()); } @@ -379,8 +381,8 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) case 1: // Use SqlServer table as source - var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); - var vectorParam = new SqlParameter(s_vectorParamName, new SqlVector(VectorFloat32TestData.testData)); + var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values ({VectorParameterName})", sourceConnection); + var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(VectorFloat32TestData.testData)); // Insert 2 rows with one non-null and null value insertCmd.Parameters.Add(vectorParam); @@ -394,7 +396,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) case 2: table = new DataTable(s_bulkCopySrcTableName); table.Columns.Add("Id", typeof(int)); - table.Columns.Add("VectorData", typeof(SqlVector)); + table.Columns.Add(VectorColumnName, typeof(SqlVector)); table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); table.Rows.Add(2, DBNull.Value); break; @@ -405,7 +407,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) //Bulkcopy from sql server table to destination table - using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, {VectorColumnName} FROM {s_bulkCopySrcTableName}", sourceConnection); using SqlDataReader reader = sourceDataCommand.ExecuteReader(); // Verify that the destination table is empty before bulk copy @@ -442,7 +444,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) Assert.Equal(2, Convert.ToInt16(countCommand.ExecuteScalar())); // Read the data from destination table as varbinary to verify the UTF-8 byte sequence - using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); + using SqlCommand verifyCommand = new SqlCommand($"SELECT {VectorColumnName} from {s_tableName}", destinationConnection); using SqlDataReader verifyReader = verifyCommand.ExecuteReader(); // Verify that we have data in the destination table @@ -479,8 +481,8 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) case 1: // Use SqlServer table as source - var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); - var vectorParam = new SqlParameter(s_vectorParamName, new SqlVector(VectorFloat32TestData.testData)); + var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values ({VectorParameterName})", sourceConnection); + var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(VectorFloat32TestData.testData)); // Insert 2 rows with one non-null and null value insertCmd.Parameters.Add(vectorParam); @@ -494,7 +496,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) case 2: table = new DataTable(s_bulkCopySrcTableName); table.Columns.Add("Id", typeof(int)); - table.Columns.Add("VectorData", typeof(SqlVector)); + table.Columns.Add(VectorColumnName, typeof(SqlVector)); table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); table.Rows.Add(2, DBNull.Value); break; @@ -503,7 +505,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) } //Bulkcopy from sql server table to destination table - using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, {VectorColumnName} FROM {s_bulkCopySrcTableName}", sourceConnection); using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); // Verify that the destination table is empty before bulk copy @@ -540,7 +542,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) Assert.Equal(2, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); // Read the data from destination table as varbinary to verify the UTF-8 byte sequence - using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); + using SqlCommand verifyCommand = new SqlCommand($"SELECT {VectorColumnName} from {s_tableName}", destinationConnection); using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(); // Verify that we have data in the destination table @@ -571,7 +573,7 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() // Insert a row so we can query it using (var insertCmd = new SqlCommand(s_insertCmdString, connection)) { - var param = insertCmd.Parameters.Add(s_vectorParamName, SqlDbTypeExtensions.Vector); + var param = insertCmd.Parameters.Add(VectorParameterName, SqlDbTypeExtensions.Vector); param.Value = new SqlVector(VectorFloat32TestData.testData); insertCmd.ExecuteNonQuery(); } @@ -603,7 +605,7 @@ public void TestInsertVectorsFloat32WithPrepare() SqlConnection conn = new SqlConnection(s_connectionString); conn.Open(); SqlCommand command = new SqlCommand(s_insertCmdString, conn); - SqlParameter vectorParam = new SqlParameter("@VectorData", SqlDbTypeExtensions.Vector); + SqlParameter vectorParam = new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector); command.Parameters.Add(vectorParam); command.Prepare(); for (int i = 0; i < 10; i++) @@ -611,7 +613,7 @@ public void TestInsertVectorsFloat32WithPrepare() vectorParam.Value = new SqlVector(new float[] { i + 0.1f, i + 0.2f, i + 0.3f, i + 0.4f, i + 0.5f, i + 0.6f }); command.ExecuteNonQuery(); } - SqlCommand validateCommand = new SqlCommand($"SELECT VectorData FROM {s_tableName}", conn); + SqlCommand validateCommand = new SqlCommand($"SELECT {VectorColumnName} FROM {s_tableName}", conn); using SqlDataReader reader = validateCommand.ExecuteReader(); int rowcnt = 0; while (reader.Read()) From 0a33c47140adcd2403a86f181533fb2370e4540a Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:56:31 +0100 Subject: [PATCH 04/26] Use generics for vector supported flag and test data --- .../VectorTest/NativeVectorFloat32Tests.cs | 5 ++- .../SQL/VectorTest/NativeVectorTestsBase.cs | 44 +++++++++++++------ 2 files changed, 34 insertions(+), 15 deletions(-) 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 ce5ff05732..68438c9d39 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -2,12 +2,15 @@ // 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.Collections.Generic; + namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest; #nullable enable -public sealed class VectorFloat32TestData : VectorTestDataBase +public sealed class VectorFloat32TestData : NativeVectorTestDataBase { + public override bool IsSupported => DataTestUtility.IsSqlVectorSupported; } public sealed class NativeVectorFloat32Tests : NativeVectorTestsBase diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index a89759a80a..447ee04f03 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -14,7 +14,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest { - public abstract class VectorTestDataBase + public abstract class NativeVectorTestDataBase where TElement : unmanaged { public const int VectorHeaderSize = 8; @@ -22,6 +22,11 @@ public abstract class VectorTestDataBase public static int vectorColumnLength = testData.Length; // Incorrect size for SqlParameter.Size public static int IncorrectParamSize = 3234; + + public abstract bool IsSupported { get; } + + public IEnumerable TestData => GetVectorFloat32TestData(); + public static IEnumerable GetVectorFloat32TestData() { // Pattern 1-4 with SqlVector(values: testData) @@ -56,7 +61,7 @@ public static IEnumerable GetVectorFloat32TestData() public abstract class NativeVectorTestsBase : IDisposable where TElement : unmanaged - where TTestData : VectorTestDataBase + where TTestData : NativeVectorTestDataBase, new() { private const string VectorColumnName = "VectorData"; private const string VectorParameterName = "@VectorData"; @@ -88,6 +93,17 @@ public abstract class NativeVectorTestsBase : IDisposable ORDER BY Id DESC; END;"; + // xUnit only allows MemberData for a test to point to static methods, properties and variables. + // This presents a problem when the sample data needs to change based upon the element type of + // the SqlVector, so this compromises: it instantiates a class derived from NativeVectorTestDataBase, + // then projects the relevant fields from it as static properties in this base class. + private static TTestData TestDataInstance => + field ??= new(); + + public static bool IsSupported => TestDataInstance.IsSupported; + + public static IEnumerable TestData => TestDataInstance.TestData; + public NativeVectorTestsBase() { using var connection = new SqlConnection(s_connectionString); @@ -151,8 +167,8 @@ private void ValidateInsertedData(SqlConnection connection, float[] expectedData } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + [ConditionalTheory(nameof(IsSupported))] + [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public void TestSqlVectorFloat32ParameterInsertionAndReads( int pattern, object value, @@ -217,8 +233,8 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] e } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + [ConditionalTheory(nameof(IsSupported))] + [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( int pattern, object value, @@ -251,8 +267,8 @@ public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( await ValidateInsertedDataAsync(conn, expectedValues, expectedLength); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + [ConditionalTheory(nameof(IsSupported))] + [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public void TestStoredProcParamsForVectorFloat32( int pattern, object value, @@ -308,8 +324,8 @@ public void TestStoredProcParamsForVectorFloat32( Assert.Throws(() => command.ExecuteNonQuery()); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + [ConditionalTheory(nameof(IsSupported))] + [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public async Task TestStoredProcParamsForVectorFloat32Async( int pattern, object value, @@ -365,7 +381,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync()); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [ConditionalTheory(nameof(IsSupported))] [InlineData(1)] [InlineData(2)] public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) @@ -464,7 +480,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [ConditionalTheory(nameof(IsSupported))] [InlineData(1)] [InlineData(2)] public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) @@ -564,7 +580,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [ConditionalFact(nameof(IsSupported))] public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() { using var connection = new SqlConnection(s_connectionString); @@ -599,7 +615,7 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() Assert.Equal(VectorFloat32TestData.testData, typedValue.Memory.ToArray()); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + [ConditionalFact(nameof(IsSupported))] public void TestInsertVectorsFloat32WithPrepare() { SqlConnection conn = new SqlConnection(s_connectionString); From 5e233670935b6f6afada4454d864139694f83841 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:03:16 +0100 Subject: [PATCH 05/26] Specify vector base type via abstract property --- .../SQL/VectorTest/NativeVectorFloat32Tests.cs | 2 ++ .../SQL/VectorTest/NativeVectorTestsBase.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) 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 68438c9d39..c326a29120 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -11,6 +11,8 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest; public sealed class VectorFloat32TestData : NativeVectorTestDataBase { public override bool IsSupported => DataTestUtility.IsSqlVectorSupported; + + public override string SqlServerTypeName => "float32"; } public sealed class NativeVectorFloat32Tests : NativeVectorTestsBase diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 447ee04f03..a57c1bbe29 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -25,6 +25,8 @@ public abstract class NativeVectorTestDataBase public abstract bool IsSupported { get; } + public abstract string SqlServerTypeName { get; } + public IEnumerable TestData => GetVectorFloat32TestData(); public static IEnumerable GetVectorFloat32TestData() @@ -71,14 +73,14 @@ public abstract class NativeVectorTestsBase : IDisposable private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); private static readonly int s_vectorDimensions = VectorFloat32TestData.vectorColumnLength; - private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}) NULL)"; - private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}) NULL)"; + private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; + private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; private static readonly string s_selectCmdString = $"SELECT {VectorColumnName} FROM {s_tableName} ORDER BY Id DESC"; private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} ({VectorColumnName}) VALUES ({VectorParameterName})"; private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); private static readonly string s_storedProcBody = $@" - {VectorParameterName} vector({s_vectorDimensions}), -- Input: Serialized float[] as JSON string - {VectorOutputParameterName} vector({s_vectorDimensions}) OUTPUT -- Output: Echoed back from latest inserted row + {VectorParameterName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}), -- Input: Serialized float[] as JSON string + {VectorOutputParameterName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) OUTPUT -- Output: Echoed back from latest inserted row AS BEGIN SET NOCOUNT ON; From fe23347c08324a24ba2433aa7098c2884f78b910 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:12:15 +0100 Subject: [PATCH 06/26] Rename various static variables to final property names --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index a57c1bbe29..24cbce70b3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -18,10 +18,10 @@ public abstract class NativeVectorTestDataBase where TElement : unmanaged { public const int VectorHeaderSize = 8; - public static float[] testData = new float[] { 1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f }; - public static int vectorColumnLength = testData.Length; + public static float[] SampleScalarData = new float[] { 1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f }; + public static int ValidSampleScalarDataLength = SampleScalarData.Length; // Incorrect size for SqlParameter.Size - public static int IncorrectParamSize = 3234; + public static int IncorrectScalarDataParameterSize = 3234; public abstract bool IsSupported { get; } @@ -31,33 +31,33 @@ public abstract class NativeVectorTestDataBase public static IEnumerable GetVectorFloat32TestData() { - // Pattern 1-4 with SqlVector(values: testData) - yield return new object[] { 1, new SqlVector(testData), testData, vectorColumnLength }; - yield return new object[] { 2, new SqlVector(testData), testData, vectorColumnLength }; - yield return new object[] { 3, new SqlVector(testData), testData, vectorColumnLength }; - yield return new object[] { 4, new SqlVector(testData), testData, vectorColumnLength }; + // Pattern 1-4 with SqlVector(values: SampleScalarData) + yield return new object[] { 1, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; + yield return new object[] { 2, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; + yield return new object[] { 3, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; + yield return new object[] { 4, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; // Pattern 1-4 with SqlVector(n) - yield return new object[] { 1, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; - yield return new object[] { 2, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; - yield return new object[] { 3, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; - yield return new object[] { 4, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; + yield return new object[] { 1, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 2, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 3, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 4, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; // Pattern 1-4 with DBNull - yield return new object[] { 1, DBNull.Value, Array.Empty(), vectorColumnLength }; - yield return new object[] { 2, DBNull.Value, Array.Empty(), vectorColumnLength }; - yield return new object[] { 3, DBNull.Value, Array.Empty(), vectorColumnLength }; - yield return new object[] { 4, DBNull.Value, Array.Empty(), vectorColumnLength }; + yield return new object[] { 1, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 2, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 3, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 4, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; // Pattern 1-4 with SqlVector.Null - yield return new object[] { 1, SqlVector.Null, Array.Empty(), vectorColumnLength }; + yield return new object[] { 1, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; // Following scenario is not supported in SqlClient. // This can only be fixed with a behavior change that SqlParameter.Value is internally set to DBNull.Value if it is set to null. //yield return new object[] { 2, SqlVector.Null, Array.Empty(), vectorColumnLength }; - yield return new object[] { 3, SqlVector.Null, Array.Empty(), vectorColumnLength }; - yield return new object[] { 4, SqlVector.Null, Array.Empty(), vectorColumnLength }; + yield return new object[] { 3, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 4, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; } } @@ -72,7 +72,7 @@ public abstract class NativeVectorTestsBase : IDisposable private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); - private static readonly int s_vectorDimensions = VectorFloat32TestData.vectorColumnLength; + private static readonly int s_vectorDimensions = VectorFloat32TestData.ValidSampleScalarDataLength; private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; private static readonly string s_selectCmdString = $"SELECT {VectorColumnName} FROM {s_tableName} ORDER BY Id DESC"; @@ -193,7 +193,7 @@ public void TestSqlVectorFloat32ParameterInsertionAndReads( 2 => new SqlParameter(VectorParameterName, value), 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, // Even if size is specified, the actual size is determined by the value passed and specified size is ignored. - 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Value = value }, _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") }; @@ -258,7 +258,7 @@ public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( }, 2 => new SqlParameter(VectorParameterName, value), 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Value = value }, _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") }; @@ -297,7 +297,7 @@ public void TestStoredProcParamsForVectorFloat32( }, 2 => new SqlParameter(VectorParameterName, value), 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Value = value }, _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") }; command.Parameters.Add(inputParam); @@ -307,7 +307,7 @@ public void TestStoredProcParamsForVectorFloat32( ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, Direction = ParameterDirection.Output, - Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) + Value = SqlVector.CreateNull(VectorFloat32TestData.ValidSampleScalarDataLength) }; command.Parameters.Add(outputParam); @@ -321,7 +321,7 @@ public void TestStoredProcParamsForVectorFloat32( // Validate error for conventional way of setting output parameters command.Parameters.Clear(); command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; + var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; command.Parameters.Add(outputParamWithoutVal); Assert.Throws(() => command.ExecuteNonQuery()); } @@ -354,7 +354,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( }, 2 => new SqlParameter(VectorParameterName, value), 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Value = value }, _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") }; command.Parameters.Add(inputParam); @@ -364,7 +364,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, Direction = ParameterDirection.Output, - Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) + Value = SqlVector.CreateNull(VectorFloat32TestData.ValidSampleScalarDataLength) }; command.Parameters.Add(outputParam); @@ -378,7 +378,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( // Validate error for conventional way of setting output parameters command.Parameters.Clear(); command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; + var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; command.Parameters.Add(outputParamWithoutVal); await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync()); } @@ -400,7 +400,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) case 1: // Use SqlServer table as source var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values ({VectorParameterName})", sourceConnection); - var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(VectorFloat32TestData.testData)); + var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(VectorFloat32TestData.SampleScalarData)); // Insert 2 rows with one non-null and null value insertCmd.Parameters.Add(vectorParam); @@ -415,7 +415,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) table = new DataTable(s_bulkCopySrcTableName); table.Columns.Add("Id", typeof(int)); table.Columns.Add(VectorColumnName, typeof(SqlVector)); - table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); + table.Rows.Add(1, new SqlVector(VectorFloat32TestData.SampleScalarData)); table.Rows.Add(2, DBNull.Value); break; default: @@ -470,8 +470,8 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) // Validate first non-null value. Assert.True(!verifyReader.IsDBNull(0), "First row in the table is null."); - Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); + Assert.Equal(VectorFloat32TestData.SampleScalarData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); // Verify that we have another row Assert.True(verifyReader.Read(), "Second row not found in the table"); @@ -479,7 +479,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) // Verify that we have encountered null. Assert.True(verifyReader.IsDBNull(0)); Assert.Equal(Array.Empty(), ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); + Assert.Equal(VectorFloat32TestData.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); } [ConditionalTheory(nameof(IsSupported))] @@ -500,7 +500,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) case 1: // Use SqlServer table as source var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values ({VectorParameterName})", sourceConnection); - var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(VectorFloat32TestData.testData)); + var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(VectorFloat32TestData.SampleScalarData)); // Insert 2 rows with one non-null and null value insertCmd.Parameters.Add(vectorParam); @@ -515,7 +515,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) table = new DataTable(s_bulkCopySrcTableName); table.Columns.Add("Id", typeof(int)); table.Columns.Add(VectorColumnName, typeof(SqlVector)); - table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); + table.Rows.Add(1, new SqlVector(VectorFloat32TestData.SampleScalarData)); table.Rows.Add(2, DBNull.Value); break; default: @@ -569,8 +569,8 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) // Validate first non-null value. Assert.True(!await verifyReader.IsDBNullAsync(0), "First row in the table is null."); var vector = await verifyReader.GetFieldValueAsync>(0); - Assert.Equal(VectorFloat32TestData.testData, vector.Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); + Assert.Equal(VectorFloat32TestData.SampleScalarData, vector.Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.SampleScalarData.Length, vector.Length); // Verify that we have another row Assert.True(await verifyReader.ReadAsync(), "Second row not found in the table"); @@ -579,7 +579,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) Assert.True(await verifyReader.IsDBNullAsync(0)); vector = await verifyReader.GetFieldValueAsync>(0); Assert.Equal(Array.Empty(), vector.Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); + Assert.Equal(VectorFloat32TestData.SampleScalarData.Length, vector.Length); } [ConditionalFact(nameof(IsSupported))] @@ -592,7 +592,7 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() using (var insertCmd = new SqlCommand(s_insertCmdString, connection)) { var param = insertCmd.Parameters.Add(VectorParameterName, SqlDbTypeExtensions.Vector); - param.Value = new SqlVector(VectorFloat32TestData.testData); + param.Value = new SqlVector(VectorFloat32TestData.SampleScalarData); insertCmd.ExecuteNonQuery(); } @@ -609,12 +609,12 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() Assert.True(reader.Read(), "No data found in the table."); object value = reader.GetValue(0); Assert.IsType>(value); - Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)value).Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.SampleScalarData, ((SqlVector)value).Memory.ToArray()); // Verify GetFieldValue> returns the correct typed value SqlVector typedValue = reader.GetFieldValue>(0); Assert.IsType>(typedValue); - Assert.Equal(VectorFloat32TestData.testData, typedValue.Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.SampleScalarData, typedValue.Memory.ToArray()); } [ConditionalFact(nameof(IsSupported))] From e68807e851a861dfc47b8e767422d2ebaf9aa20f Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:31:57 +0100 Subject: [PATCH 07/26] Specify IncorrectScalarDataParameterSize via abstract property --- .../VectorTest/NativeVectorFloat32Tests.cs | 2 + .../SQL/VectorTest/NativeVectorTestsBase.cs | 81 ++++++------------- 2 files changed, 25 insertions(+), 58 deletions(-) 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 c326a29120..1d38961ec1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -10,6 +10,8 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest; public sealed class VectorFloat32TestData : NativeVectorTestDataBase { + public override int IncorrectScalarDataParameterSize => 3234; + public override bool IsSupported => DataTestUtility.IsSqlVectorSupported; public override string SqlServerTypeName => "float32"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 24cbce70b3..3a41aa7506 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -21,7 +21,7 @@ public abstract class NativeVectorTestDataBase public static float[] SampleScalarData = new float[] { 1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f }; public static int ValidSampleScalarDataLength = SampleScalarData.Length; // Incorrect size for SqlParameter.Size - public static int IncorrectScalarDataParameterSize = 3234; + public abstract int IncorrectScalarDataParameterSize { get; } public abstract bool IsSupported { get; } @@ -124,6 +124,22 @@ public void Dispose() DataTestUtility.DropStoredProcedure(connection, s_storedProcName); } + private static SqlParameter GetParameterByPattern(int pattern, object value) => + pattern switch + { + 1 => new SqlParameter + { + ParameterName = VectorParameterName, + SqlDbType = SqlDbTypeExtensions.Vector, + Value = value + }, + 2 => new SqlParameter(VectorParameterName, value), + 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, + // Even if size is specified, the actual size is determined by the value passed and specified size is ignored. + 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + private void ValidateSqlVectorFloat32Object(bool isNull, SqlVector sqlVectorFloat32, float[] expectedData, int expectedLength) { Assert.Equal(expectedData, sqlVectorFloat32.Memory.ToArray()); @@ -181,21 +197,7 @@ public void TestSqlVectorFloat32ParameterInsertionAndReads( conn.Open(); using var insertCmd = new SqlCommand(s_insertCmdString, conn); - - SqlParameter param = pattern switch - { - 1 => new SqlParameter - { - ParameterName = VectorParameterName, - SqlDbType = SqlDbTypeExtensions.Vector, - Value = value - }, - 2 => new SqlParameter(VectorParameterName, value), - 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, - // Even if size is specified, the actual size is determined by the value passed and specified size is ignored. - 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Value = value }, - _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") - }; + SqlParameter param = GetParameterByPattern(pattern, value); insertCmd.Parameters.Add(param); Assert.Equal(1, insertCmd.ExecuteNonQuery()); @@ -247,20 +249,7 @@ public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( await conn.OpenAsync(); using var insertCmd = new SqlCommand(s_insertCmdString, conn); - - SqlParameter param = pattern switch - { - 1 => new SqlParameter - { - ParameterName = VectorParameterName, - SqlDbType = (SqlDbType)36, // SqlDbTypeExtension.Vector - Value = value - }, - 2 => new SqlParameter(VectorParameterName, value), - 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Value = value }, - _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") - }; + SqlParameter param = GetParameterByPattern(pattern, value); insertCmd.Parameters.Add(param); Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); @@ -287,19 +276,7 @@ public void TestStoredProcParamsForVectorFloat32( }; // Set input and output parameters - SqlParameter inputParam = pattern switch - { - 1 => new SqlParameter - { - ParameterName = VectorParameterName, - SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector - Value = value - }, - 2 => new SqlParameter(VectorParameterName, value), - 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Value = value }, - _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") - }; + SqlParameter inputParam = GetParameterByPattern(pattern, value); command.Parameters.Add(inputParam); var outputParam = new SqlParameter @@ -321,7 +298,7 @@ public void TestStoredProcParamsForVectorFloat32( // Validate error for conventional way of setting output parameters command.Parameters.Clear(); command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; + var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; command.Parameters.Add(outputParamWithoutVal); Assert.Throws(() => command.ExecuteNonQuery()); } @@ -344,19 +321,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( }; // Set input and output parameters - SqlParameter inputParam = pattern switch - { - 1 => new SqlParameter - { - ParameterName = VectorParameterName, - SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector - Value = value - }, - 2 => new SqlParameter(VectorParameterName, value), - 3 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector) { Value = value }, - 4 => new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Value = value }, - _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") - }; + SqlParameter inputParam = GetParameterByPattern(pattern, value); command.Parameters.Add(inputParam); var outputParam = new SqlParameter @@ -378,7 +343,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( // Validate error for conventional way of setting output parameters command.Parameters.Clear(); command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; + var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; command.Parameters.Add(outputParamWithoutVal); await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync()); } From 396792660763ba97aa87758047732ace45b78962 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:40:40 +0100 Subject: [PATCH 08/26] Specify SampleScalarData[Length] via abstract property --- .../VectorTest/NativeVectorFloat32Tests.cs | 2 + .../SQL/VectorTest/NativeVectorTestsBase.cs | 64 +++++++++---------- 2 files changed, 34 insertions(+), 32 deletions(-) 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 1d38961ec1..3c3e303569 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -10,6 +10,8 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest; public sealed class VectorFloat32TestData : NativeVectorTestDataBase { + public override float[] SampleScalarData => [1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f]; + public override int IncorrectScalarDataParameterSize => 3234; public override bool IsSupported => DataTestUtility.IsSqlVectorSupported; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 3a41aa7506..ceb31319df 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -18,8 +18,8 @@ public abstract class NativeVectorTestDataBase where TElement : unmanaged { public const int VectorHeaderSize = 8; - public static float[] SampleScalarData = new float[] { 1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f }; - public static int ValidSampleScalarDataLength = SampleScalarData.Length; + public abstract TElement[] SampleScalarData { get; } + public int ValidSampleScalarDataLength => SampleScalarData.Length; // Incorrect size for SqlParameter.Size public abstract int IncorrectScalarDataParameterSize { get; } @@ -29,13 +29,13 @@ public abstract class NativeVectorTestDataBase public IEnumerable TestData => GetVectorFloat32TestData(); - public static IEnumerable GetVectorFloat32TestData() + public IEnumerable GetVectorFloat32TestData() { - // Pattern 1-4 with SqlVector(values: SampleScalarData) - yield return new object[] { 1, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; - yield return new object[] { 2, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; - yield return new object[] { 3, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; - yield return new object[] { 4, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; + // Pattern 1-4 with SqlVector(values: SampleScalarData) + yield return new object[] { 1, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; + yield return new object[] { 2, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; + yield return new object[] { 3, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; + yield return new object[] { 4, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; // Pattern 1-4 with SqlVector(n) yield return new object[] { 1, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; @@ -72,7 +72,7 @@ public abstract class NativeVectorTestsBase : IDisposable private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); - private static readonly int s_vectorDimensions = VectorFloat32TestData.ValidSampleScalarDataLength; + private static readonly int s_vectorDimensions = TestDataInstance.ValidSampleScalarDataLength; private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; private static readonly string s_selectCmdString = $"SELECT {VectorColumnName} FROM {s_tableName} ORDER BY Id DESC"; @@ -284,7 +284,7 @@ public void TestStoredProcParamsForVectorFloat32( ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, Direction = ParameterDirection.Output, - Value = SqlVector.CreateNull(VectorFloat32TestData.ValidSampleScalarDataLength) + Value = SqlVector.CreateNull(TestDataInstance.ValidSampleScalarDataLength) }; command.Parameters.Add(outputParam); @@ -329,7 +329,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, Direction = ParameterDirection.Output, - Value = SqlVector.CreateNull(VectorFloat32TestData.ValidSampleScalarDataLength) + Value = SqlVector.CreateNull(TestDataInstance.ValidSampleScalarDataLength) }; command.Parameters.Add(outputParam); @@ -365,7 +365,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) case 1: // Use SqlServer table as source var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values ({VectorParameterName})", sourceConnection); - var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(VectorFloat32TestData.SampleScalarData)); + var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); // Insert 2 rows with one non-null and null value insertCmd.Parameters.Add(vectorParam); @@ -379,8 +379,8 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) case 2: table = new DataTable(s_bulkCopySrcTableName); table.Columns.Add("Id", typeof(int)); - table.Columns.Add(VectorColumnName, typeof(SqlVector)); - table.Rows.Add(1, new SqlVector(VectorFloat32TestData.SampleScalarData)); + table.Columns.Add(VectorColumnName, typeof(SqlVector)); + table.Rows.Add(1, new SqlVector(TestDataInstance.SampleScalarData)); table.Rows.Add(2, DBNull.Value); break; default: @@ -435,8 +435,8 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) // Validate first non-null value. Assert.True(!verifyReader.IsDBNull(0), "First row in the table is null."); - Assert.Equal(VectorFloat32TestData.SampleScalarData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); + Assert.Equal(TestDataInstance.SampleScalarData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal(TestDataInstance.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); // Verify that we have another row Assert.True(verifyReader.Read(), "Second row not found in the table"); @@ -444,7 +444,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) // Verify that we have encountered null. Assert.True(verifyReader.IsDBNull(0)); Assert.Equal(Array.Empty(), ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); + Assert.Equal(TestDataInstance.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); } [ConditionalTheory(nameof(IsSupported))] @@ -465,7 +465,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) case 1: // Use SqlServer table as source var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values ({VectorParameterName})", sourceConnection); - var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(VectorFloat32TestData.SampleScalarData)); + var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); // Insert 2 rows with one non-null and null value insertCmd.Parameters.Add(vectorParam); @@ -479,8 +479,8 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) case 2: table = new DataTable(s_bulkCopySrcTableName); table.Columns.Add("Id", typeof(int)); - table.Columns.Add(VectorColumnName, typeof(SqlVector)); - table.Rows.Add(1, new SqlVector(VectorFloat32TestData.SampleScalarData)); + table.Columns.Add(VectorColumnName, typeof(SqlVector)); + table.Rows.Add(1, new SqlVector(TestDataInstance.SampleScalarData)); table.Rows.Add(2, DBNull.Value); break; default: @@ -533,18 +533,18 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) // Validate first non-null value. Assert.True(!await verifyReader.IsDBNullAsync(0), "First row in the table is null."); - var vector = await verifyReader.GetFieldValueAsync>(0); - Assert.Equal(VectorFloat32TestData.SampleScalarData, vector.Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.SampleScalarData.Length, vector.Length); + var vector = await verifyReader.GetFieldValueAsync>(0); + Assert.Equal(TestDataInstance.SampleScalarData, vector.Memory.ToArray()); + Assert.Equal(TestDataInstance.SampleScalarData.Length, vector.Length); // Verify that we have another row Assert.True(await verifyReader.ReadAsync(), "Second row not found in the table"); // Verify that we have encountered null. Assert.True(await verifyReader.IsDBNullAsync(0)); - vector = await verifyReader.GetFieldValueAsync>(0); - Assert.Equal(Array.Empty(), vector.Memory.ToArray()); - Assert.Equal(VectorFloat32TestData.SampleScalarData.Length, vector.Length); + vector = await verifyReader.GetFieldValueAsync>(0); + Assert.Equal(Array.Empty(), vector.Memory.ToArray()); + Assert.Equal(TestDataInstance.SampleScalarData.Length, vector.Length); } [ConditionalFact(nameof(IsSupported))] @@ -557,7 +557,7 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() using (var insertCmd = new SqlCommand(s_insertCmdString, connection)) { var param = insertCmd.Parameters.Add(VectorParameterName, SqlDbTypeExtensions.Vector); - param.Value = new SqlVector(VectorFloat32TestData.SampleScalarData); + param.Value = new SqlVector(TestDataInstance.SampleScalarData); insertCmd.ExecuteNonQuery(); } @@ -573,13 +573,13 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() // Verify that GetValue returns an instance consistent with GetFieldType Assert.True(reader.Read(), "No data found in the table."); object value = reader.GetValue(0); - Assert.IsType>(value); - Assert.Equal(VectorFloat32TestData.SampleScalarData, ((SqlVector)value).Memory.ToArray()); + Assert.IsType>(value); + Assert.Equal(TestDataInstance.SampleScalarData, ((SqlVector)value).Memory.ToArray()); // Verify GetFieldValue> returns the correct typed value - SqlVector typedValue = reader.GetFieldValue>(0); - Assert.IsType>(typedValue); - Assert.Equal(VectorFloat32TestData.SampleScalarData, typedValue.Memory.ToArray()); + SqlVector typedValue = reader.GetFieldValue>(0); + Assert.IsType>(typedValue); + Assert.Equal(TestDataInstance.SampleScalarData, typedValue.Memory.ToArray()); } [ConditionalFact(nameof(IsSupported))] From cbe8f1cf3e248b79648290cd0182ec4e6d0e14b7 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:44:43 +0100 Subject: [PATCH 09/26] Remove some remaining SqlVector references --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index ceb31319df..f80807cb26 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -37,27 +37,27 @@ public IEnumerable GetVectorFloat32TestData() yield return new object[] { 3, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; yield return new object[] { 4, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; - // Pattern 1-4 with SqlVector(n) - yield return new object[] { 1, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 2, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 3, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 4, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; + // Pattern 1-4 with SqlVector(n) + yield return new object[] { 1, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 2, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 3, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 4, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; // Pattern 1-4 with DBNull - yield return new object[] { 1, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 2, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 3, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 4, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 1, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 2, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 3, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 4, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; - // Pattern 1-4 with SqlVector.Null - yield return new object[] { 1, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; + // Pattern 1-4 with SqlVector.Null + yield return new object[] { 1, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; // Following scenario is not supported in SqlClient. // This can only be fixed with a behavior change that SqlParameter.Value is internally set to DBNull.Value if it is set to null. - //yield return new object[] { 2, SqlVector.Null, Array.Empty(), vectorColumnLength }; + //yield return new object[] { 2, SqlVector.Null, Array.Empty(), vectorColumnLength }; - yield return new object[] { 3, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 4, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 3, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; + yield return new object[] { 4, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; } } @@ -160,16 +160,16 @@ private void ValidateInsertedData(SqlConnection connection, float[] expectedData using var reader = selectCmd.ExecuteReader(); Assert.True(reader.Read(), "No data found in the table."); - //For both null and non-null cases, validate the SqlVector object - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + //For both null and non-null cases, validate the SqlVector object + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); if (!reader.IsDBNull(0)) { - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetFieldValue(0))); @@ -212,16 +212,16 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] e using var reader = await selectCmd.ExecuteReaderAsync(); Assert.True(await reader.ReadAsync(), "No data found in the table."); - //For both null and non-null cases, validate the SqlVector object - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + //For both null and non-null cases, validate the SqlVector object + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); if (!await reader.IsDBNullAsync(0)) { - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); Assert.Equal(expectedData, JsonSerializer.Deserialize(await reader.GetFieldValueAsync(0))); @@ -284,7 +284,7 @@ public void TestStoredProcParamsForVectorFloat32( ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, Direction = ParameterDirection.Output, - Value = SqlVector.CreateNull(TestDataInstance.ValidSampleScalarDataLength) + Value = SqlVector.CreateNull(TestDataInstance.ValidSampleScalarDataLength) }; command.Parameters.Add(outputParam); @@ -292,7 +292,7 @@ public void TestStoredProcParamsForVectorFloat32( command.ExecuteNonQuery(); // Validate the output parameter - var vector = (SqlVector)outputParam.Value; + var vector = (SqlVector)outputParam.Value; ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); // Validate error for conventional way of setting output parameters @@ -329,7 +329,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, Direction = ParameterDirection.Output, - Value = SqlVector.CreateNull(TestDataInstance.ValidSampleScalarDataLength) + Value = SqlVector.CreateNull(TestDataInstance.ValidSampleScalarDataLength) }; command.Parameters.Add(outputParam); @@ -337,7 +337,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( await command.ExecuteNonQueryAsync(); // Validate the output parameter - var vector = (SqlVector)outputParam.Value; + var vector = (SqlVector)outputParam.Value; ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); // Validate error for conventional way of setting output parameters @@ -443,7 +443,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) // Verify that we have encountered null. Assert.True(verifyReader.IsDBNull(0)); - Assert.Equal(Array.Empty(), ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal(Array.Empty(), ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); Assert.Equal(TestDataInstance.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); } @@ -564,11 +564,11 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() using var selectCmd = new SqlCommand(s_selectCmdString, connection); using var reader = selectCmd.ExecuteReader(); - // Verify GetFieldType returns SqlVector for the vector column - Assert.Equal(typeof(SqlVector), reader.GetFieldType(0)); + // Verify GetFieldType returns SqlVector for the vector column + Assert.Equal(typeof(SqlVector), reader.GetFieldType(0)); - // Verify GetProviderSpecificFieldType also returns SqlVector - Assert.Equal(typeof(SqlVector), reader.GetProviderSpecificFieldType(0)); + // Verify GetProviderSpecificFieldType also returns SqlVector + Assert.Equal(typeof(SqlVector), reader.GetProviderSpecificFieldType(0)); // Verify that GetValue returns an instance consistent with GetFieldType Assert.True(reader.Read(), "No data found in the table."); @@ -576,7 +576,7 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() Assert.IsType>(value); Assert.Equal(TestDataInstance.SampleScalarData, ((SqlVector)value).Memory.ToArray()); - // Verify GetFieldValue> returns the correct typed value + // Verify GetFieldValue> returns the correct typed value SqlVector typedValue = reader.GetFieldValue>(0); Assert.IsType>(typedValue); Assert.Equal(TestDataInstance.SampleScalarData, typedValue.Memory.ToArray()); From b460b423d2fab32981dcf8d28550fb7e69c17e56 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:47:27 +0100 Subject: [PATCH 10/26] Make ValidateSqlVectorFloat32Object generic --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index f80807cb26..644b848beb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -140,39 +140,39 @@ private static SqlParameter GetParameterByPattern(int pattern, object value) => _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") }; - private void ValidateSqlVectorFloat32Object(bool isNull, SqlVector sqlVectorFloat32, float[] expectedData, int expectedLength) + private static void ValidateSqlVectorObject(bool isNull, SqlVector sqlVector, TElement[] expectedData, int expectedLength) { - Assert.Equal(expectedData, sqlVectorFloat32.Memory.ToArray()); - Assert.Equal(expectedLength, sqlVectorFloat32.Length); + Assert.Equal(expectedData, sqlVector.Memory.ToArray()); + Assert.Equal(expectedLength, sqlVector.Length); if (!isNull) { - Assert.False(sqlVectorFloat32.IsNull, "IsNull set to true for a non-null value"); + Assert.False(sqlVector.IsNull, "IsNull set to true for a non-null value"); } else { - Assert.True(sqlVectorFloat32.IsNull, "IsNull set to false for a null value"); + Assert.True(sqlVector.IsNull, "IsNull set to false for a null value"); } } - private void ValidateInsertedData(SqlConnection connection, float[] expectedData, int expectedLength) + private void ValidateInsertedData(SqlConnection connection, TElement[] expectedData, int expectedLength) { using var selectCmd = new SqlCommand(s_selectCmdString, connection); using var reader = selectCmd.ExecuteReader(); Assert.True(reader.Read(), "No data found in the table."); //For both null and non-null cases, validate the SqlVector object - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorObject(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength); + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); if (!reader.IsDBNull(0)) { - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength); - ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetFieldValue(0))); + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetFieldValue(0))); } else { @@ -206,25 +206,25 @@ public void TestSqlVectorFloat32ParameterInsertionAndReads( ValidateInsertedData(conn, expectedValues, expectedLength); } - private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] expectedData, int expectedLength) + private async Task ValidateInsertedDataAsync(SqlConnection connection, TElement[] expectedData, int expectedLength) { using var selectCmd = new SqlCommand(s_selectCmdString, connection); using var reader = await selectCmd.ExecuteReaderAsync(); Assert.True(await reader.ReadAsync(), "No data found in the table."); //For both null and non-null cases, validate the SqlVector object - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); if (!await reader.IsDBNullAsync(0)) { - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength); - ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); - Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); - Assert.Equal(expectedData, JsonSerializer.Deserialize(await reader.GetFieldValueAsync(0))); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader[VectorColumnName], expectedData, expectedLength); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); + Assert.Equal(expectedData, JsonSerializer.Deserialize(await reader.GetFieldValueAsync(0))); } else { @@ -293,7 +293,7 @@ public void TestStoredProcParamsForVectorFloat32( // Validate the output parameter var vector = (SqlVector)outputParam.Value; - ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); + ValidateSqlVectorObject(vector.IsNull, vector, expectedValues, expectedLength); // Validate error for conventional way of setting output parameters command.Parameters.Clear(); @@ -338,7 +338,7 @@ public async Task TestStoredProcParamsForVectorFloat32Async( // Validate the output parameter var vector = (SqlVector)outputParam.Value; - ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); + ValidateSqlVectorObject(vector.IsNull, vector, expectedValues, expectedLength); // Validate error for conventional way of setting output parameters command.Parameters.Clear(); From 399a76e21807f075f02177df8848abdd1d40a2ff Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:52:45 +0100 Subject: [PATCH 11/26] Thread TElement through test signatures --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 644b848beb..fdb425a64c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -188,10 +188,10 @@ private void ValidateInsertedData(SqlConnection connection, TElement[] expectedD [ConditionalTheory(nameof(IsSupported))] [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public void TestSqlVectorFloat32ParameterInsertionAndReads( - int pattern, - object value, - float[] expectedValues, - int expectedLength) + int pattern, + object value, + TElement[] expectedValues, + int expectedLength) { using var conn = new SqlConnection(s_connectionString); conn.Open(); @@ -240,10 +240,10 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, TElement[ [ConditionalTheory(nameof(IsSupported))] [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( - int pattern, - object value, - float[] expectedValues, - int expectedLength) + int pattern, + object value, + TElement[] expectedValues, + int expectedLength) { using var conn = new SqlConnection(s_connectionString); await conn.OpenAsync(); @@ -261,10 +261,10 @@ public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( [ConditionalTheory(nameof(IsSupported))] [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public void TestStoredProcParamsForVectorFloat32( - int pattern, - object value, - float[] expectedValues, - int expectedLength) + int pattern, + object value, + TElement[] expectedValues, + int expectedLength) { //Create SP for test using var conn = new SqlConnection(s_connectionString); @@ -306,10 +306,10 @@ public void TestStoredProcParamsForVectorFloat32( [ConditionalTheory(nameof(IsSupported))] [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public async Task TestStoredProcParamsForVectorFloat32Async( - int pattern, - object value, - float[] expectedValues, - int expectedLength) + int pattern, + object value, + TElement[] expectedValues, + int expectedLength) { //Create SP for test using var conn = new SqlConnection(s_connectionString); From ea29de67329d5340ba79f13f98ed9ad4a56d6df1 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:57:59 +0100 Subject: [PATCH 12/26] Remove redundant GetVectorFloat32TestData method --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index fdb425a64c..aee457a182 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -27,38 +27,36 @@ public abstract class NativeVectorTestDataBase public abstract string SqlServerTypeName { get; } - public IEnumerable TestData => GetVectorFloat32TestData(); - - public IEnumerable GetVectorFloat32TestData() - { + public IEnumerable TestData => + [ // Pattern 1-4 with SqlVector(values: SampleScalarData) - yield return new object[] { 1, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; - yield return new object[] { 2, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; - yield return new object[] { 3, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; - yield return new object[] { 4, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength }; + [ 1, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength ], + [ 2, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength ], + [ 3, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength ], + [ 4, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength ], // Pattern 1-4 with SqlVector(n) - yield return new object[] { 1, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 2, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 3, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 4, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength }; + [ 1, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength ], + [ 2, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength ], + [ 3, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength ], + [ 4, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength ], // Pattern 1-4 with DBNull - yield return new object[] { 1, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 2, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 3, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 4, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength }; + [ 1, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength ], + [ 2, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength ], + [ 3, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength ], + [ 4, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength ], // Pattern 1-4 with SqlVector.Null - yield return new object[] { 1, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; + [ 1, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength ], // Following scenario is not supported in SqlClient. // This can only be fixed with a behavior change that SqlParameter.Value is internally set to DBNull.Value if it is set to null. - //yield return new object[] { 2, SqlVector.Null, Array.Empty(), vectorColumnLength }; + // [ 2, SqlVector.Null, Array.Empty(), vectorColumnLength ], - yield return new object[] { 3, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; - yield return new object[] { 4, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength }; - } + [ 3, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength ], + [ 4, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength ] + ]; } public abstract class NativeVectorTestsBase : IDisposable From 93624923ed49449f180abd55049a4ffadbaaf83f Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:59:16 +0100 Subject: [PATCH 13/26] Cleanup test names --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index aee457a182..55b74f7605 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -77,7 +77,7 @@ public abstract class NativeVectorTestsBase : IDisposable private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} ({VectorColumnName}) VALUES ({VectorParameterName})"; private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); private static readonly string s_storedProcBody = $@" - {VectorParameterName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}), -- Input: Serialized float[] as JSON string + {VectorParameterName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}), -- Input: Serialized TElement[] as JSON string {VectorOutputParameterName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) OUTPUT -- Output: Echoed back from latest inserted row AS BEGIN @@ -185,7 +185,7 @@ private void ValidateInsertedData(SqlConnection connection, TElement[] expectedD [ConditionalTheory(nameof(IsSupported))] [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] - public void TestSqlVectorFloat32ParameterInsertionAndReads( + public void TestSqlVectorParameterInsertionAndReads( int pattern, object value, TElement[] expectedValues, @@ -237,7 +237,7 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, TElement[ [ConditionalTheory(nameof(IsSupported))] [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] - public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( + public async Task TestSqlVectorParameterInsertionAndReadsAsync( int pattern, object value, TElement[] expectedValues, @@ -258,7 +258,7 @@ public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( [ConditionalTheory(nameof(IsSupported))] [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] - public void TestStoredProcParamsForVectorFloat32( + public void TestStoredProcParamsForVector( int pattern, object value, TElement[] expectedValues, @@ -303,7 +303,7 @@ public void TestStoredProcParamsForVectorFloat32( [ConditionalTheory(nameof(IsSupported))] [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] - public async Task TestStoredProcParamsForVectorFloat32Async( + public async Task TestStoredProcParamsForVectorAsync( int pattern, object value, TElement[] expectedValues, From 8e103c08cd358c8cc80cd4e43925e72bf32987bf Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:22:24 +0100 Subject: [PATCH 14/26] Make TestInsertVectorsFloat32WithPrepare generic --- .../VectorTest/NativeVectorFloat32Tests.cs | 20 +++++++++++++ .../SQL/VectorTest/NativeVectorTestsBase.cs | 28 +++++++++++++++---- 2 files changed, 43 insertions(+), 5 deletions(-) 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 3c3e303569..64d0a8716b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -12,6 +12,26 @@ public sealed class VectorFloat32TestData : NativeVectorTestDataBase { public override float[] SampleScalarData => [1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f]; + public override float[,] SampleDataSet + { + get + { + float[,] sampleData = new float[10, ValidSampleScalarDataLength]; + + for (int i = 0; i < sampleData.GetLength(0); i++) + { + float baseValue = i * 10; + + for (int j = 0; j < sampleData.GetLength(1); j++) + { + sampleData[i, j] = baseValue + (j * 0.1f); + } + } + + return sampleData; + } + } + public override int IncorrectScalarDataParameterSize => 3234; public override bool IsSupported => DataTestUtility.IsSqlVectorSupported; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 55b74f7605..1dd961e94e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -19,6 +19,9 @@ public abstract class NativeVectorTestDataBase { public const int VectorHeaderSize = 8; public abstract TElement[] SampleScalarData { get; } + + public abstract TElement[,] SampleDataSet { get; } + public int ValidSampleScalarDataLength => SampleScalarData.Length; // Incorrect size for SqlParameter.Size public abstract int IncorrectScalarDataParameterSize { get; } @@ -581,7 +584,7 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() } [ConditionalFact(nameof(IsSupported))] - public void TestInsertVectorsFloat32WithPrepare() + public void TestInsertVectorsWithPrepare() { SqlConnection conn = new SqlConnection(s_connectionString); conn.Open(); @@ -589,22 +592,37 @@ public void TestInsertVectorsFloat32WithPrepare() SqlParameter vectorParam = new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector); command.Parameters.Add(vectorParam); command.Prepare(); - for (int i = 0; i < 10; i++) + + TElement[,] sampleDataSet = TestDataInstance.SampleDataSet; + for (int i = 0; i < sampleDataSet.GetLength(0); i++) { - vectorParam.Value = new SqlVector(new float[] { i + 0.1f, i + 0.2f, i + 0.3f, i + 0.4f, i + 0.5f, i + 0.6f }); + TElement[] rowData = GetMultidimensionalArraySlice(sampleDataSet, i); + vectorParam.Value = new SqlVector(rowData); command.ExecuteNonQuery(); } + SqlCommand validateCommand = new SqlCommand($"SELECT {VectorColumnName} FROM {s_tableName}", conn); using SqlDataReader reader = validateCommand.ExecuteReader(); int rowcnt = 0; while (reader.Read()) { - float[] expectedData = new float[] { rowcnt + 0.1f, rowcnt + 0.2f, rowcnt + 0.3f, rowcnt + 0.4f, rowcnt + 0.5f, rowcnt + 0.6f }; - float[] dbData = reader.GetSqlVector(0).Memory.ToArray(); + TElement[] expectedData = GetMultidimensionalArraySlice(sampleDataSet, rowcnt); + TElement[] dbData = reader.GetSqlVector(0).Memory.ToArray(); Assert.Equal(expectedData, dbData); rowcnt++; } Assert.Equal(10, rowcnt); + + static TElement[] GetMultidimensionalArraySlice(TElement[,] sourceArray, int dimension) + { + TElement[] dst = new TElement[sourceArray.GetLength(1)]; + + for (int i = 0; i < dst.Length; i++) + { + dst[i] = sourceArray[dimension, i]; + } + return dst; + } } } } From ae6208f50367ea63895154baaa21ed1a9d5869a3 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:32:13 +0100 Subject: [PATCH 15/26] Reorder and rename static readonly variables --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 1dd961e94e..88b679893e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -70,16 +70,9 @@ public abstract class NativeVectorTestsBase : IDisposable private const string VectorParameterName = "@VectorData"; private const string VectorOutputParameterName = "@OutputVectorData"; - private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; - private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); - private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); private static readonly int s_vectorDimensions = TestDataInstance.ValidSampleScalarDataLength; - private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; - private static readonly string s_selectCmdString = $"SELECT {VectorColumnName} FROM {s_tableName} ORDER BY Id DESC"; - private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} ({VectorColumnName}) VALUES ({VectorParameterName})"; - private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); - private static readonly string s_storedProcBody = $@" + private static readonly string s_spBodyTemplate = $@" {VectorParameterName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}), -- Input: Serialized TElement[] as JSON string {VectorOutputParameterName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) OUTPUT -- Output: Echoed back from latest inserted row AS @@ -95,6 +88,13 @@ public abstract class NativeVectorTestsBase : IDisposable FROM {s_tableName} ORDER BY Id DESC; END;"; + private static readonly string s_selectCommandTemplate = $"SELECT {VectorColumnName} FROM {s_tableName} ORDER BY Id DESC"; + private static readonly string s_insertCommandTemplate = $"INSERT INTO {s_tableName} ({VectorColumnName}) VALUES ({VectorParameterName})"; + + private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; + private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); + private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); + private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); // xUnit only allows MemberData for a test to point to static methods, properties and variables. // This presents a problem when the sample data needs to change based upon the element type of @@ -112,8 +112,8 @@ public NativeVectorTestsBase() using var connection = new SqlConnection(s_connectionString); connection.Open(); DataTestUtility.CreateTable(connection, s_tableName, s_tableDefinition); - DataTestUtility.CreateTable(connection, s_bulkCopySrcTableName, s_bulkCopySrcTableDef); - DataTestUtility.CreateSP(connection, s_storedProcName, s_storedProcBody); + DataTestUtility.CreateTable(connection, s_bulkCopySrcTableName, s_tableDefinition); + DataTestUtility.CreateSP(connection, s_storedProcName, s_spBodyTemplate); } public void Dispose() @@ -157,7 +157,7 @@ private static void ValidateSqlVectorObject(bool isNull, SqlVector sql private void ValidateInsertedData(SqlConnection connection, TElement[] expectedData, int expectedLength) { - using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var selectCmd = new SqlCommand(s_selectCommandTemplate, connection); using var reader = selectCmd.ExecuteReader(); Assert.True(reader.Read(), "No data found in the table."); @@ -197,7 +197,7 @@ public void TestSqlVectorParameterInsertionAndReads( using var conn = new SqlConnection(s_connectionString); conn.Open(); - using var insertCmd = new SqlCommand(s_insertCmdString, conn); + using var insertCmd = new SqlCommand(s_insertCommandTemplate, conn); SqlParameter param = GetParameterByPattern(pattern, value); insertCmd.Parameters.Add(param); @@ -209,7 +209,7 @@ public void TestSqlVectorParameterInsertionAndReads( private async Task ValidateInsertedDataAsync(SqlConnection connection, TElement[] expectedData, int expectedLength) { - using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var selectCmd = new SqlCommand(s_selectCommandTemplate, connection); using var reader = await selectCmd.ExecuteReaderAsync(); Assert.True(await reader.ReadAsync(), "No data found in the table."); @@ -249,7 +249,7 @@ public async Task TestSqlVectorParameterInsertionAndReadsAsync( using var conn = new SqlConnection(s_connectionString); await conn.OpenAsync(); - using var insertCmd = new SqlCommand(s_insertCmdString, conn); + using var insertCmd = new SqlCommand(s_insertCommandTemplate, conn); SqlParameter param = GetParameterByPattern(pattern, value); insertCmd.Parameters.Add(param); @@ -270,7 +270,7 @@ public void TestStoredProcParamsForVector( //Create SP for test using var conn = new SqlConnection(s_connectionString); conn.Open(); - DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); + DataTestUtility.CreateSP(conn, s_storedProcName, s_spBodyTemplate); using var command = new SqlCommand(s_storedProcName, conn) { CommandType = CommandType.StoredProcedure @@ -315,7 +315,7 @@ public async Task TestStoredProcParamsForVectorAsync( //Create SP for test using var conn = new SqlConnection(s_connectionString); await conn.OpenAsync(); - DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); + DataTestUtility.CreateSP(conn, s_storedProcName, s_spBodyTemplate); using var command = new SqlCommand(s_storedProcName, conn) { CommandType = CommandType.StoredProcedure @@ -555,14 +555,14 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() connection.Open(); // Insert a row so we can query it - using (var insertCmd = new SqlCommand(s_insertCmdString, connection)) + using (var insertCmd = new SqlCommand(s_insertCommandTemplate, connection)) { var param = insertCmd.Parameters.Add(VectorParameterName, SqlDbTypeExtensions.Vector); param.Value = new SqlVector(TestDataInstance.SampleScalarData); insertCmd.ExecuteNonQuery(); } - using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var selectCmd = new SqlCommand(s_selectCommandTemplate, connection); using var reader = selectCmd.ExecuteReader(); // Verify GetFieldType returns SqlVector for the vector column @@ -588,7 +588,7 @@ public void TestInsertVectorsWithPrepare() { SqlConnection conn = new SqlConnection(s_connectionString); conn.Open(); - SqlCommand command = new SqlCommand(s_insertCmdString, conn); + SqlCommand command = new SqlCommand(s_insertCommandTemplate, conn); SqlParameter vectorParam = new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector); command.Parameters.Add(vectorParam); command.Prepare(); From 7b9df5811d77afb0a6252bcb111fbdf505084a48 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:35:03 +0100 Subject: [PATCH 16/26] Prepare for RAII database objects --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 88b679893e..12526ba348 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -91,7 +91,8 @@ public abstract class NativeVectorTestsBase : IDisposable private static readonly string s_selectCommandTemplate = $"SELECT {VectorColumnName} FROM {s_tableName} ORDER BY Id DESC"; private static readonly string s_insertCommandTemplate = $"INSERT INTO {s_tableName} ({VectorColumnName}) VALUES ({VectorParameterName})"; - private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; + private readonly string _connectionString; + private readonly SqlConnection _managementConnection; private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); @@ -109,7 +110,10 @@ public abstract class NativeVectorTestsBase : IDisposable public NativeVectorTestsBase() { - using var connection = new SqlConnection(s_connectionString); + _connectionString = DataTestUtility.TCPConnectionString; + _managementConnection = new SqlConnection(_connectionString); + + using var connection = new SqlConnection(_connectionString); connection.Open(); DataTestUtility.CreateTable(connection, s_tableName, s_tableDefinition); DataTestUtility.CreateTable(connection, s_bulkCopySrcTableName, s_tableDefinition); @@ -118,7 +122,7 @@ public NativeVectorTestsBase() public void Dispose() { - using var connection = new SqlConnection(s_connectionString); + using var connection = new SqlConnection(_connectionString); connection.Open(); DataTestUtility.DropTable(connection, s_tableName); DataTestUtility.DropTable(connection, s_bulkCopySrcTableName); @@ -194,7 +198,7 @@ public void TestSqlVectorParameterInsertionAndReads( TElement[] expectedValues, int expectedLength) { - using var conn = new SqlConnection(s_connectionString); + using var conn = new SqlConnection(_connectionString); conn.Open(); using var insertCmd = new SqlCommand(s_insertCommandTemplate, conn); @@ -246,7 +250,7 @@ public async Task TestSqlVectorParameterInsertionAndReadsAsync( TElement[] expectedValues, int expectedLength) { - using var conn = new SqlConnection(s_connectionString); + using var conn = new SqlConnection(_connectionString); await conn.OpenAsync(); using var insertCmd = new SqlCommand(s_insertCommandTemplate, conn); @@ -268,7 +272,7 @@ public void TestStoredProcParamsForVector( int expectedLength) { //Create SP for test - using var conn = new SqlConnection(s_connectionString); + using var conn = new SqlConnection(_connectionString); conn.Open(); DataTestUtility.CreateSP(conn, s_storedProcName, s_spBodyTemplate); using var command = new SqlCommand(s_storedProcName, conn) @@ -313,7 +317,7 @@ public async Task TestStoredProcParamsForVectorAsync( int expectedLength) { //Create SP for test - using var conn = new SqlConnection(s_connectionString); + using var conn = new SqlConnection(_connectionString); await conn.OpenAsync(); DataTestUtility.CreateSP(conn, s_storedProcName, s_spBodyTemplate); using var command = new SqlCommand(s_storedProcName, conn) @@ -355,9 +359,9 @@ public async Task TestStoredProcParamsForVectorAsync( public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) { //Setup source with test data and create destination table for bulkcopy. - SqlConnection sourceConnection = new SqlConnection(s_connectionString); + SqlConnection sourceConnection = new SqlConnection(_connectionString); sourceConnection.Open(); - SqlConnection destinationConnection = new SqlConnection(s_connectionString); + SqlConnection destinationConnection = new SqlConnection(_connectionString); destinationConnection.Open(); DataTable table = null; switch (bulkCopySourceMode) @@ -454,9 +458,9 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) { //Setup source with test data and create destination table for bulkcopy. - SqlConnection sourceConnection = new SqlConnection(s_connectionString); + SqlConnection sourceConnection = new SqlConnection(_connectionString); await sourceConnection.OpenAsync(); - SqlConnection destinationConnection = new SqlConnection(s_connectionString); + SqlConnection destinationConnection = new SqlConnection(_connectionString); await destinationConnection.OpenAsync(); DataTable table = null; @@ -551,7 +555,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) [ConditionalFact(nameof(IsSupported))] public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() { - using var connection = new SqlConnection(s_connectionString); + using var connection = new SqlConnection(_connectionString); connection.Open(); // Insert a row so we can query it @@ -586,7 +590,7 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() [ConditionalFact(nameof(IsSupported))] public void TestInsertVectorsWithPrepare() { - SqlConnection conn = new SqlConnection(s_connectionString); + SqlConnection conn = new SqlConnection(_connectionString); conn.Open(); SqlCommand command = new SqlCommand(s_insertCommandTemplate, conn); SqlParameter vectorParam = new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector); From f3c9b2a66b6fc02aef523e87defad50e9ffb8fa0 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:45:00 +0100 Subject: [PATCH 17/26] RAII vector table --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 12526ba348..98e95e6fef 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -8,6 +8,7 @@ using System.Data.SqlTypes; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Microsoft.Data.SqlTypes; using Xunit; using Xunit.Abstractions; @@ -80,20 +81,24 @@ public abstract class NativeVectorTestsBase : IDisposable SET NOCOUNT ON; -- Insert into vector table - INSERT INTO {s_tableName} ({VectorColumnName}) + INSERT INTO {{0}} ({VectorColumnName}) VALUES ({VectorParameterName}); -- Retrieve latest entry (assumes auto-incrementing ID) SELECT TOP 1 {VectorOutputParameterName} = {VectorColumnName} - FROM {s_tableName} + FROM {{0}} ORDER BY Id DESC; END;"; - private static readonly string s_selectCommandTemplate = $"SELECT {VectorColumnName} FROM {s_tableName} ORDER BY Id DESC"; - private static readonly string s_insertCommandTemplate = $"INSERT INTO {s_tableName} ({VectorColumnName}) VALUES ({VectorParameterName})"; + private static readonly string s_selectCommandTemplate = $"SELECT {VectorColumnName} FROM {{0}} ORDER BY Id DESC"; + private static readonly string s_insertCommandTemplate = $"INSERT INTO {{0}} ({VectorColumnName}) VALUES ({VectorParameterName})"; private readonly string _connectionString; private readonly SqlConnection _managementConnection; - private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); + private readonly Table _vectorTable; + + private readonly string _selectCommand; + private readonly string _insertCommand; + private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); @@ -112,19 +117,25 @@ public NativeVectorTestsBase() { _connectionString = DataTestUtility.TCPConnectionString; _managementConnection = new SqlConnection(_connectionString); + _vectorTable = new Table(_managementConnection, "VectorTestTable", s_tableDefinition); + + _selectCommand = string.Format(s_selectCommandTemplate, _vectorTable.Name); + _insertCommand = string.Format(s_insertCommandTemplate, _vectorTable.Name); using var connection = new SqlConnection(_connectionString); connection.Open(); - DataTestUtility.CreateTable(connection, s_tableName, s_tableDefinition); DataTestUtility.CreateTable(connection, s_bulkCopySrcTableName, s_tableDefinition); - DataTestUtility.CreateSP(connection, s_storedProcName, s_spBodyTemplate); + DataTestUtility.CreateSP(connection, s_storedProcName, string.Format(s_spBodyTemplate, _vectorTable.Name)); } public void Dispose() { + using (_managementConnection) + using (_vectorTable) + { } + using var connection = new SqlConnection(_connectionString); connection.Open(); - DataTestUtility.DropTable(connection, s_tableName); DataTestUtility.DropTable(connection, s_bulkCopySrcTableName); DataTestUtility.DropStoredProcedure(connection, s_storedProcName); } @@ -161,7 +172,7 @@ private static void ValidateSqlVectorObject(bool isNull, SqlVector sql private void ValidateInsertedData(SqlConnection connection, TElement[] expectedData, int expectedLength) { - using var selectCmd = new SqlCommand(s_selectCommandTemplate, connection); + using var selectCmd = new SqlCommand(_selectCommand, connection); using var reader = selectCmd.ExecuteReader(); Assert.True(reader.Read(), "No data found in the table."); @@ -201,7 +212,7 @@ public void TestSqlVectorParameterInsertionAndReads( using var conn = new SqlConnection(_connectionString); conn.Open(); - using var insertCmd = new SqlCommand(s_insertCommandTemplate, conn); + using var insertCmd = new SqlCommand(_insertCommand, conn); SqlParameter param = GetParameterByPattern(pattern, value); insertCmd.Parameters.Add(param); @@ -213,7 +224,7 @@ public void TestSqlVectorParameterInsertionAndReads( private async Task ValidateInsertedDataAsync(SqlConnection connection, TElement[] expectedData, int expectedLength) { - using var selectCmd = new SqlCommand(s_selectCommandTemplate, connection); + using var selectCmd = new SqlCommand(_selectCommand, connection); using var reader = await selectCmd.ExecuteReaderAsync(); Assert.True(await reader.ReadAsync(), "No data found in the table."); @@ -253,7 +264,7 @@ public async Task TestSqlVectorParameterInsertionAndReadsAsync( using var conn = new SqlConnection(_connectionString); await conn.OpenAsync(); - using var insertCmd = new SqlCommand(s_insertCommandTemplate, conn); + using var insertCmd = new SqlCommand(_insertCommand, conn); SqlParameter param = GetParameterByPattern(pattern, value); insertCmd.Parameters.Add(param); @@ -274,7 +285,7 @@ public void TestStoredProcParamsForVector( //Create SP for test using var conn = new SqlConnection(_connectionString); conn.Open(); - DataTestUtility.CreateSP(conn, s_storedProcName, s_spBodyTemplate); + DataTestUtility.CreateSP(conn, s_storedProcName, string.Format(s_spBodyTemplate, _vectorTable.Name)); using var command = new SqlCommand(s_storedProcName, conn) { CommandType = CommandType.StoredProcedure @@ -319,7 +330,7 @@ public async Task TestStoredProcParamsForVectorAsync( //Create SP for test using var conn = new SqlConnection(_connectionString); await conn.OpenAsync(); - DataTestUtility.CreateSP(conn, s_storedProcName, s_spBodyTemplate); + DataTestUtility.CreateSP(conn, s_storedProcName, string.Format(s_spBodyTemplate, _vectorTable.Name)); using var command = new SqlCommand(s_storedProcName, conn) { CommandType = CommandType.StoredProcedure @@ -399,13 +410,13 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) using SqlDataReader reader = sourceDataCommand.ExecuteReader(); // Verify that the destination table is empty before bulk copy - using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); // Initialize bulk copy configuration using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) { - DestinationTableName = s_tableName, + DestinationTableName = _vectorTable.Name, }; try @@ -432,7 +443,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) Assert.Equal(2, Convert.ToInt16(countCommand.ExecuteScalar())); // Read the data from destination table as varbinary to verify the UTF-8 byte sequence - using SqlCommand verifyCommand = new SqlCommand($"SELECT {VectorColumnName} from {s_tableName}", destinationConnection); + using SqlCommand verifyCommand = new SqlCommand($"SELECT {VectorColumnName} from {_vectorTable.Name}", destinationConnection); using SqlDataReader verifyReader = verifyCommand.ExecuteReader(); // Verify that we have data in the destination table @@ -497,13 +508,13 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); // Verify that the destination table is empty before bulk copy - using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); // Initialize bulk copy configuration using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) { - DestinationTableName = s_tableName, + DestinationTableName = _vectorTable.Name, }; try @@ -530,7 +541,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) Assert.Equal(2, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); // Read the data from destination table as varbinary to verify the UTF-8 byte sequence - using SqlCommand verifyCommand = new SqlCommand($"SELECT {VectorColumnName} from {s_tableName}", destinationConnection); + using SqlCommand verifyCommand = new SqlCommand($"SELECT {VectorColumnName} from {_vectorTable.Name}", destinationConnection); using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(); // Verify that we have data in the destination table @@ -559,14 +570,14 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() connection.Open(); // Insert a row so we can query it - using (var insertCmd = new SqlCommand(s_insertCommandTemplate, connection)) + using (var insertCmd = new SqlCommand(_insertCommand, connection)) { var param = insertCmd.Parameters.Add(VectorParameterName, SqlDbTypeExtensions.Vector); param.Value = new SqlVector(TestDataInstance.SampleScalarData); insertCmd.ExecuteNonQuery(); } - using var selectCmd = new SqlCommand(s_selectCommandTemplate, connection); + using var selectCmd = new SqlCommand(_selectCommand, connection); using var reader = selectCmd.ExecuteReader(); // Verify GetFieldType returns SqlVector for the vector column @@ -592,7 +603,7 @@ public void TestInsertVectorsWithPrepare() { SqlConnection conn = new SqlConnection(_connectionString); conn.Open(); - SqlCommand command = new SqlCommand(s_insertCommandTemplate, conn); + SqlCommand command = new SqlCommand(_insertCommand, conn); SqlParameter vectorParam = new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector); command.Parameters.Add(vectorParam); command.Prepare(); @@ -605,7 +616,7 @@ public void TestInsertVectorsWithPrepare() command.ExecuteNonQuery(); } - SqlCommand validateCommand = new SqlCommand($"SELECT {VectorColumnName} FROM {s_tableName}", conn); + SqlCommand validateCommand = new SqlCommand($"SELECT {VectorColumnName} FROM {_vectorTable.Name}", conn); using SqlDataReader reader = validateCommand.ExecuteReader(); int rowcnt = 0; while (reader.Read()) From 481d3f411cf4fbd4113220c6d31f536163b4ffb7 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:50:43 +0100 Subject: [PATCH 18/26] RAII bulk copy source table --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 98e95e6fef..39829616bc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -95,11 +95,11 @@ public abstract class NativeVectorTestsBase : IDisposable private readonly string _connectionString; private readonly SqlConnection _managementConnection; private readonly Table _vectorTable; + private readonly Table _bulkCopySourceTable; private readonly string _selectCommand; private readonly string _insertCommand; - private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); // xUnit only allows MemberData for a test to point to static methods, properties and variables. @@ -118,13 +118,13 @@ public NativeVectorTestsBase() _connectionString = DataTestUtility.TCPConnectionString; _managementConnection = new SqlConnection(_connectionString); _vectorTable = new Table(_managementConnection, "VectorTestTable", s_tableDefinition); + _bulkCopySourceTable = new Table(_managementConnection, "VectorBulkCopyTestTable", s_tableDefinition); _selectCommand = string.Format(s_selectCommandTemplate, _vectorTable.Name); _insertCommand = string.Format(s_insertCommandTemplate, _vectorTable.Name); using var connection = new SqlConnection(_connectionString); connection.Open(); - DataTestUtility.CreateTable(connection, s_bulkCopySrcTableName, s_tableDefinition); DataTestUtility.CreateSP(connection, s_storedProcName, string.Format(s_spBodyTemplate, _vectorTable.Name)); } @@ -132,11 +132,11 @@ public void Dispose() { using (_managementConnection) using (_vectorTable) + using (_bulkCopySourceTable) { } using var connection = new SqlConnection(_connectionString); connection.Open(); - DataTestUtility.DropTable(connection, s_bulkCopySrcTableName); DataTestUtility.DropStoredProcedure(connection, s_storedProcName); } @@ -380,7 +380,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) case 1: // Use SqlServer table as source - var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values ({VectorParameterName})", sourceConnection); + var insertCmd = new SqlCommand($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); // Insert 2 rows with one non-null and null value @@ -393,7 +393,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) insertCmd.Parameters.Clear(); break; case 2: - table = new DataTable(s_bulkCopySrcTableName); + table = new DataTable(_bulkCopySourceTable.Name); table.Columns.Add("Id", typeof(int)); table.Columns.Add(VectorColumnName, typeof(SqlVector)); table.Rows.Add(1, new SqlVector(TestDataInstance.SampleScalarData)); @@ -406,7 +406,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) //Bulkcopy from sql server table to destination table - using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, {VectorColumnName} FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); using SqlDataReader reader = sourceDataCommand.ExecuteReader(); // Verify that the destination table is empty before bulk copy @@ -480,7 +480,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) case 1: // Use SqlServer table as source - var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values ({VectorParameterName})", sourceConnection); + var insertCmd = new SqlCommand($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); // Insert 2 rows with one non-null and null value @@ -493,7 +493,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) insertCmd.Parameters.Clear(); break; case 2: - table = new DataTable(s_bulkCopySrcTableName); + table = new DataTable(_bulkCopySourceTable.Name); table.Columns.Add("Id", typeof(int)); table.Columns.Add(VectorColumnName, typeof(SqlVector)); table.Rows.Add(1, new SqlVector(TestDataInstance.SampleScalarData)); @@ -504,7 +504,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) } //Bulkcopy from sql server table to destination table - using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, {VectorColumnName} FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); // Verify that the destination table is empty before bulk copy From 311b61c2b0ff7768970d969394934c028ff51350 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:53:51 +0100 Subject: [PATCH 19/26] RAII stored procedure --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 39829616bc..16949b96f7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -96,12 +96,11 @@ public abstract class NativeVectorTestsBase : IDisposable private readonly SqlConnection _managementConnection; private readonly Table _vectorTable; private readonly Table _bulkCopySourceTable; + private readonly StoredProcedure _vectorProcedure; private readonly string _selectCommand; private readonly string _insertCommand; - private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); - // xUnit only allows MemberData for a test to point to static methods, properties and variables. // This presents a problem when the sample data needs to change based upon the element type of // the SqlVector, so this compromises: it instantiates a class derived from NativeVectorTestDataBase, @@ -119,13 +118,10 @@ public NativeVectorTestsBase() _managementConnection = new SqlConnection(_connectionString); _vectorTable = new Table(_managementConnection, "VectorTestTable", s_tableDefinition); _bulkCopySourceTable = new Table(_managementConnection, "VectorBulkCopyTestTable", s_tableDefinition); + _vectorProcedure = new StoredProcedure(_managementConnection, "VectorsAsVarcharSp", string.Format(s_spBodyTemplate, _vectorTable.Name)); _selectCommand = string.Format(s_selectCommandTemplate, _vectorTable.Name); _insertCommand = string.Format(s_insertCommandTemplate, _vectorTable.Name); - - using var connection = new SqlConnection(_connectionString); - connection.Open(); - DataTestUtility.CreateSP(connection, s_storedProcName, string.Format(s_spBodyTemplate, _vectorTable.Name)); } public void Dispose() @@ -133,11 +129,8 @@ public void Dispose() using (_managementConnection) using (_vectorTable) using (_bulkCopySourceTable) + using (_vectorProcedure) { } - - using var connection = new SqlConnection(_connectionString); - connection.Open(); - DataTestUtility.DropStoredProcedure(connection, s_storedProcName); } private static SqlParameter GetParameterByPattern(int pattern, object value) => @@ -282,11 +275,9 @@ public void TestStoredProcParamsForVector( TElement[] expectedValues, int expectedLength) { - //Create SP for test using var conn = new SqlConnection(_connectionString); conn.Open(); - DataTestUtility.CreateSP(conn, s_storedProcName, string.Format(s_spBodyTemplate, _vectorTable.Name)); - using var command = new SqlCommand(s_storedProcName, conn) + using var command = new SqlCommand(_vectorProcedure.Name, conn) { CommandType = CommandType.StoredProcedure }; @@ -327,11 +318,9 @@ public async Task TestStoredProcParamsForVectorAsync( TElement[] expectedValues, int expectedLength) { - //Create SP for test using var conn = new SqlConnection(_connectionString); await conn.OpenAsync(); - DataTestUtility.CreateSP(conn, s_storedProcName, string.Format(s_spBodyTemplate, _vectorTable.Name)); - using var command = new SqlCommand(s_storedProcName, conn) + using var command = new SqlCommand(_vectorProcedure.Name, conn) { CommandType = CommandType.StoredProcedure }; From 0401f8d77238b92c785323606a52409b21d569a8 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:56:27 +0100 Subject: [PATCH 20/26] Style cleanup: remove unused using, reorder member --- .../ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs | 2 -- .../ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) 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 64d0a8716b..39aa74d463 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -2,8 +2,6 @@ // 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.Collections.Generic; - namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest; #nullable enable diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 16949b96f7..938d64b037 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -11,19 +11,16 @@ using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Microsoft.Data.SqlTypes; using Xunit; -using Xunit.Abstractions; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest { public abstract class NativeVectorTestDataBase where TElement : unmanaged { - public const int VectorHeaderSize = 8; public abstract TElement[] SampleScalarData { get; } public abstract TElement[,] SampleDataSet { get; } - public int ValidSampleScalarDataLength => SampleScalarData.Length; // Incorrect size for SqlParameter.Size public abstract int IncorrectScalarDataParameterSize { get; } @@ -31,6 +28,8 @@ public abstract class NativeVectorTestDataBase public abstract string SqlServerTypeName { get; } + public int ValidSampleScalarDataLength => SampleScalarData.Length; + public IEnumerable TestData => [ // Pattern 1-4 with SqlVector(values: SampleScalarData) From 25d264b4ba384c10bf1896ff333765bbfa253926 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:04:31 +0100 Subject: [PATCH 21/26] Minor style/perf cleanup Removed use of var, ensured that all variables are disposed of where trivial --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 140 +++++++++--------- 1 file changed, 72 insertions(+), 68 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 938d64b037..689e5f8215 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -130,6 +130,8 @@ public void Dispose() using (_bulkCopySourceTable) using (_vectorProcedure) { } + + GC.SuppressFinalize(this); } private static SqlParameter GetParameterByPattern(int pattern, object value) => @@ -164,8 +166,8 @@ private static void ValidateSqlVectorObject(bool isNull, SqlVector sql private void ValidateInsertedData(SqlConnection connection, TElement[] expectedData, int expectedLength) { - using var selectCmd = new SqlCommand(_selectCommand, connection); - using var reader = selectCmd.ExecuteReader(); + using SqlCommand selectCmd = new(_selectCommand, connection); + using SqlDataReader reader = selectCmd.ExecuteReader(); Assert.True(reader.Read(), "No data found in the table."); //For both null and non-null cases, validate the SqlVector object @@ -201,10 +203,10 @@ public void TestSqlVectorParameterInsertionAndReads( TElement[] expectedValues, int expectedLength) { - using var conn = new SqlConnection(_connectionString); + using SqlConnection conn = new(_connectionString); conn.Open(); - using var insertCmd = new SqlCommand(_insertCommand, conn); + using SqlCommand insertCmd = new(_insertCommand, conn); SqlParameter param = GetParameterByPattern(pattern, value); insertCmd.Parameters.Add(param); @@ -216,8 +218,8 @@ public void TestSqlVectorParameterInsertionAndReads( private async Task ValidateInsertedDataAsync(SqlConnection connection, TElement[] expectedData, int expectedLength) { - using var selectCmd = new SqlCommand(_selectCommand, connection); - using var reader = await selectCmd.ExecuteReaderAsync(); + using SqlCommand selectCmd = new(_selectCommand, connection); + using SqlDataReader reader = await selectCmd.ExecuteReaderAsync(); Assert.True(await reader.ReadAsync(), "No data found in the table."); //For both null and non-null cases, validate the SqlVector object @@ -253,10 +255,10 @@ public async Task TestSqlVectorParameterInsertionAndReadsAsync( TElement[] expectedValues, int expectedLength) { - using var conn = new SqlConnection(_connectionString); + using SqlConnection conn = new(_connectionString); await conn.OpenAsync(); - using var insertCmd = new SqlCommand(_insertCommand, conn); + using SqlCommand insertCmd = new(_insertCommand, conn); SqlParameter param = GetParameterByPattern(pattern, value); insertCmd.Parameters.Add(param); @@ -274,9 +276,9 @@ public void TestStoredProcParamsForVector( TElement[] expectedValues, int expectedLength) { - using var conn = new SqlConnection(_connectionString); + using SqlConnection conn = new(_connectionString); conn.Open(); - using var command = new SqlCommand(_vectorProcedure.Name, conn) + using SqlCommand command = new(_vectorProcedure.Name, conn) { CommandType = CommandType.StoredProcedure }; @@ -285,7 +287,7 @@ public void TestStoredProcParamsForVector( SqlParameter inputParam = GetParameterByPattern(pattern, value); command.Parameters.Add(inputParam); - var outputParam = new SqlParameter + SqlParameter outputParam = new() { ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, @@ -298,13 +300,13 @@ public void TestStoredProcParamsForVector( command.ExecuteNonQuery(); // Validate the output parameter - var vector = (SqlVector)outputParam.Value; + SqlVector vector = (SqlVector)outputParam.Value; ValidateSqlVectorObject(vector.IsNull, vector, expectedValues, expectedLength); // Validate error for conventional way of setting output parameters command.Parameters.Clear(); command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; + SqlParameter outputParamWithoutVal = new(VectorOutputParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; command.Parameters.Add(outputParamWithoutVal); Assert.Throws(() => command.ExecuteNonQuery()); } @@ -317,9 +319,9 @@ public async Task TestStoredProcParamsForVectorAsync( TElement[] expectedValues, int expectedLength) { - using var conn = new SqlConnection(_connectionString); + using SqlConnection conn = new(_connectionString); await conn.OpenAsync(); - using var command = new SqlCommand(_vectorProcedure.Name, conn) + using SqlCommand command = new(_vectorProcedure.Name, conn) { CommandType = CommandType.StoredProcedure }; @@ -328,7 +330,7 @@ public async Task TestStoredProcParamsForVectorAsync( SqlParameter inputParam = GetParameterByPattern(pattern, value); command.Parameters.Add(inputParam); - var outputParam = new SqlParameter + SqlParameter outputParam = new() { ParameterName = VectorOutputParameterName, SqlDbType = SqlDbTypeExtensions.Vector, @@ -341,13 +343,13 @@ public async Task TestStoredProcParamsForVectorAsync( await command.ExecuteNonQueryAsync(); // Validate the output parameter - var vector = (SqlVector)outputParam.Value; + SqlVector vector = (SqlVector)outputParam.Value; ValidateSqlVectorObject(vector.IsNull, vector, expectedValues, expectedLength); // Validate error for conventional way of setting output parameters command.Parameters.Clear(); command.Parameters.Add(inputParam); - var outputParamWithoutVal = new SqlParameter(VectorOutputParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; + SqlParameter outputParamWithoutVal = new(VectorOutputParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; command.Parameters.Add(outputParamWithoutVal); await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync()); } @@ -358,28 +360,30 @@ public async Task TestStoredProcParamsForVectorAsync( public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) { //Setup source with test data and create destination table for bulkcopy. - SqlConnection sourceConnection = new SqlConnection(_connectionString); + using SqlConnection sourceConnection = new(_connectionString); sourceConnection.Open(); - SqlConnection destinationConnection = new SqlConnection(_connectionString); + using SqlConnection destinationConnection = new(_connectionString); destinationConnection.Open(); DataTable table = null; switch (bulkCopySourceMode) { case 1: - // Use SqlServer table as source - var insertCmd = new SqlCommand($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); - var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); - - // Insert 2 rows with one non-null and null value - insertCmd.Parameters.Add(vectorParam); - Assert.Equal(1, insertCmd.ExecuteNonQuery()); - insertCmd.Parameters.Clear(); - vectorParam.Value = DBNull.Value; - insertCmd.Parameters.Add(vectorParam); - Assert.Equal(1, insertCmd.ExecuteNonQuery()); - insertCmd.Parameters.Clear(); - break; + { + // Use SqlServer table as source + using SqlCommand insertCmd = new($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); + SqlParameter vectorParam = new(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); + + // Insert 2 rows with one non-null and null value + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + vectorParam.Value = DBNull.Value; + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + break; + } case 2: table = new DataTable(_bulkCopySourceTable.Name); table.Columns.Add("Id", typeof(int)); @@ -391,18 +395,16 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); } - - //Bulkcopy from sql server table to destination table - using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); + using SqlCommand sourceDataCommand = new($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); using SqlDataReader reader = sourceDataCommand.ExecuteReader(); // Verify that the destination table is empty before bulk copy - using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); + using SqlCommand countCommand = new($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); // Initialize bulk copy configuration - using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + using SqlBulkCopy bulkCopy = new(destinationConnection) { DestinationTableName = _vectorTable.Name, }; @@ -431,7 +433,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) Assert.Equal(2, Convert.ToInt16(countCommand.ExecuteScalar())); // Read the data from destination table as varbinary to verify the UTF-8 byte sequence - using SqlCommand verifyCommand = new SqlCommand($"SELECT {VectorColumnName} from {_vectorTable.Name}", destinationConnection); + using SqlCommand verifyCommand = new($"SELECT {VectorColumnName} from {_vectorTable.Name}", destinationConnection); using SqlDataReader verifyReader = verifyCommand.ExecuteReader(); // Verify that we have data in the destination table @@ -457,9 +459,9 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) { //Setup source with test data and create destination table for bulkcopy. - SqlConnection sourceConnection = new SqlConnection(_connectionString); + using SqlConnection sourceConnection = new(_connectionString); await sourceConnection.OpenAsync(); - SqlConnection destinationConnection = new SqlConnection(_connectionString); + using SqlConnection destinationConnection = new(_connectionString); await destinationConnection.OpenAsync(); DataTable table = null; @@ -467,19 +469,21 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) { case 1: - // Use SqlServer table as source - var insertCmd = new SqlCommand($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); - var vectorParam = new SqlParameter(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); - - // Insert 2 rows with one non-null and null value - insertCmd.Parameters.Add(vectorParam); - Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); - insertCmd.Parameters.Clear(); - vectorParam.Value = DBNull.Value; - insertCmd.Parameters.Add(vectorParam); - Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); - insertCmd.Parameters.Clear(); - break; + { + // Use SqlServer table as source + using SqlCommand insertCmd = new($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); + SqlParameter vectorParam = new(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); + + // Insert 2 rows with one non-null and null value + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + vectorParam.Value = DBNull.Value; + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + break; + } case 2: table = new DataTable(_bulkCopySourceTable.Name); table.Columns.Add("Id", typeof(int)); @@ -492,15 +496,15 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) } //Bulkcopy from sql server table to destination table - using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); + using SqlCommand sourceDataCommand = new($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); // Verify that the destination table is empty before bulk copy - using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); + using SqlCommand countCommand = new($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); // Initialize bulk copy configuration - using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + using SqlBulkCopy bulkCopy = new(destinationConnection) { DestinationTableName = _vectorTable.Name, }; @@ -529,7 +533,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) Assert.Equal(2, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); // Read the data from destination table as varbinary to verify the UTF-8 byte sequence - using SqlCommand verifyCommand = new SqlCommand($"SELECT {VectorColumnName} from {_vectorTable.Name}", destinationConnection); + using SqlCommand verifyCommand = new($"SELECT {VectorColumnName} from {_vectorTable.Name}", destinationConnection); using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(); // Verify that we have data in the destination table @@ -537,7 +541,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) // Validate first non-null value. Assert.True(!await verifyReader.IsDBNullAsync(0), "First row in the table is null."); - var vector = await verifyReader.GetFieldValueAsync>(0); + SqlVector vector = await verifyReader.GetFieldValueAsync>(0); Assert.Equal(TestDataInstance.SampleScalarData, vector.Memory.ToArray()); Assert.Equal(TestDataInstance.SampleScalarData.Length, vector.Length); @@ -554,19 +558,19 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) [ConditionalFact(nameof(IsSupported))] public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() { - using var connection = new SqlConnection(_connectionString); + using SqlConnection connection = new(_connectionString); connection.Open(); // Insert a row so we can query it - using (var insertCmd = new SqlCommand(_insertCommand, connection)) + using (SqlCommand insertCmd = new(_insertCommand, connection)) { - var param = insertCmd.Parameters.Add(VectorParameterName, SqlDbTypeExtensions.Vector); + SqlParameter param = insertCmd.Parameters.Add(VectorParameterName, SqlDbTypeExtensions.Vector); param.Value = new SqlVector(TestDataInstance.SampleScalarData); insertCmd.ExecuteNonQuery(); } - using var selectCmd = new SqlCommand(_selectCommand, connection); - using var reader = selectCmd.ExecuteReader(); + using SqlCommand selectCmd = new(_selectCommand, connection); + using SqlDataReader reader = selectCmd.ExecuteReader(); // Verify GetFieldType returns SqlVector for the vector column Assert.Equal(typeof(SqlVector), reader.GetFieldType(0)); @@ -589,10 +593,10 @@ public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() [ConditionalFact(nameof(IsSupported))] public void TestInsertVectorsWithPrepare() { - SqlConnection conn = new SqlConnection(_connectionString); + using SqlConnection conn = new(_connectionString); conn.Open(); - SqlCommand command = new SqlCommand(_insertCommand, conn); - SqlParameter vectorParam = new SqlParameter(VectorParameterName, SqlDbTypeExtensions.Vector); + using SqlCommand command = new(_insertCommand, conn); + SqlParameter vectorParam = new(VectorParameterName, SqlDbTypeExtensions.Vector); command.Parameters.Add(vectorParam); command.Prepare(); @@ -604,7 +608,7 @@ public void TestInsertVectorsWithPrepare() command.ExecuteNonQuery(); } - SqlCommand validateCommand = new SqlCommand($"SELECT {VectorColumnName} FROM {_vectorTable.Name}", conn); + using SqlCommand validateCommand = new($"SELECT {VectorColumnName} FROM {_vectorTable.Name}", conn); using SqlDataReader reader = validateCommand.ExecuteReader(); int rowcnt = 0; while (reader.Read()) From 2a64506d1d00b4b73980e20718a66c1625773a92 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:06:09 +0100 Subject: [PATCH 22/26] Assertion cleanup --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 61 +++++++------------ 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 689e5f8215..8f241ce597 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -409,24 +409,16 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) DestinationTableName = _vectorTable.Name, }; - try - { - switch (bulkCopySourceMode) - { - case 1: - bulkCopy.WriteToServer(reader); - break; - case 2: - bulkCopy.WriteToServer(table); - break; - default: - throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); - } - } - catch (Exception ex) + switch (bulkCopySourceMode) { - // If bulk copy fails, fail the test with the exception message - Assert.Fail($"Bulk copy failed: {ex.Message}"); + case 1: + bulkCopy.WriteToServer(reader); + break; + case 2: + bulkCopy.WriteToServer(table); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); } // Verify that the 2 rows from the source table have been copied into the destination table. @@ -440,7 +432,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) Assert.True(verifyReader.Read(), "No data found in destination table after bulk copy."); // Validate first non-null value. - Assert.True(!verifyReader.IsDBNull(0), "First row in the table is null."); + Assert.False(verifyReader.IsDBNull(0), "First row in the table is null."); Assert.Equal(TestDataInstance.SampleScalarData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); Assert.Equal(TestDataInstance.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); @@ -449,7 +441,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) // Verify that we have encountered null. Assert.True(verifyReader.IsDBNull(0)); - Assert.Equal(Array.Empty(), ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal([], ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); Assert.Equal(TestDataInstance.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); } @@ -509,24 +501,17 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) DestinationTableName = _vectorTable.Name, }; - try - { // Perform bulkcopy - switch (bulkCopySourceMode) - { - case 1: - await bulkCopy.WriteToServerAsync(reader); - break; - case 2: - await bulkCopy.WriteToServerAsync(table); - break; - default: - throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); - } - } - catch (Exception ex) + // Perform bulkcopy + switch (bulkCopySourceMode) { - // If bulk copy fails, fail the test with the exception message - Assert.Fail($"Bulk copy failed: {ex.Message}"); + case 1: + await bulkCopy.WriteToServerAsync(reader); + break; + case 2: + await bulkCopy.WriteToServerAsync(table); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); } // Verify that the 2 rows from the source table have been copied into the destination table. @@ -540,7 +525,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) Assert.True(await verifyReader.ReadAsync(), "No data found in destination table after bulk copy."); // Validate first non-null value. - Assert.True(!await verifyReader.IsDBNullAsync(0), "First row in the table is null."); + Assert.False(await verifyReader.IsDBNullAsync(0), "First row in the table is null."); SqlVector vector = await verifyReader.GetFieldValueAsync>(0); Assert.Equal(TestDataInstance.SampleScalarData, vector.Memory.ToArray()); Assert.Equal(TestDataInstance.SampleScalarData.Length, vector.Length); @@ -551,7 +536,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) // Verify that we have encountered null. Assert.True(await verifyReader.IsDBNullAsync(0)); vector = await verifyReader.GetFieldValueAsync>(0); - Assert.Equal(Array.Empty(), vector.Memory.ToArray()); + Assert.Equal([], vector.Memory.ToArray()); Assert.Equal(TestDataInstance.SampleScalarData.Length, vector.Length); } From bb611560a6759d22c38723e6aecb5ba4b56484c8 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:39:13 +0100 Subject: [PATCH 23/26] Enable nullability annotations --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 8f241ce597..d0dced4855 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -12,6 +12,8 @@ using Microsoft.Data.SqlTypes; using Xunit; +#nullable enable + namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest { public abstract class NativeVectorTestDataBase @@ -30,7 +32,7 @@ public abstract class NativeVectorTestDataBase public int ValidSampleScalarDataLength => SampleScalarData.Length; - public IEnumerable TestData => + public IEnumerable TestData => [ // Pattern 1-4 with SqlVector(values: SampleScalarData) [ 1, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength ], @@ -70,27 +72,6 @@ public abstract class NativeVectorTestsBase : IDisposable private const string VectorParameterName = "@VectorData"; private const string VectorOutputParameterName = "@OutputVectorData"; - private static readonly int s_vectorDimensions = TestDataInstance.ValidSampleScalarDataLength; - private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; - private static readonly string s_spBodyTemplate = $@" - {VectorParameterName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}), -- Input: Serialized TElement[] as JSON string - {VectorOutputParameterName} vector({s_vectorDimensions}, {TestDataInstance.SqlServerTypeName}) OUTPUT -- Output: Echoed back from latest inserted row - AS - BEGIN - SET NOCOUNT ON; - - -- Insert into vector table - INSERT INTO {{0}} ({VectorColumnName}) - VALUES ({VectorParameterName}); - - -- Retrieve latest entry (assumes auto-incrementing ID) - SELECT TOP 1 {VectorOutputParameterName} = {VectorColumnName} - FROM {{0}} - ORDER BY Id DESC; - END;"; - private static readonly string s_selectCommandTemplate = $"SELECT {VectorColumnName} FROM {{0}} ORDER BY Id DESC"; - private static readonly string s_insertCommandTemplate = $"INSERT INTO {{0}} ({VectorColumnName}) VALUES ({VectorParameterName})"; - private readonly string _connectionString; private readonly SqlConnection _managementConnection; private readonly Table _vectorTable; @@ -109,18 +90,38 @@ public abstract class NativeVectorTestsBase : IDisposable public static bool IsSupported => TestDataInstance.IsSupported; - public static IEnumerable TestData => TestDataInstance.TestData; + public static IEnumerable TestData => TestDataInstance.TestData; public NativeVectorTestsBase() { + int vectorDimensions = TestDataInstance.ValidSampleScalarDataLength; + string tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; + _connectionString = DataTestUtility.TCPConnectionString; _managementConnection = new SqlConnection(_connectionString); - _vectorTable = new Table(_managementConnection, "VectorTestTable", s_tableDefinition); - _bulkCopySourceTable = new Table(_managementConnection, "VectorBulkCopyTestTable", s_tableDefinition); - _vectorProcedure = new StoredProcedure(_managementConnection, "VectorsAsVarcharSp", string.Format(s_spBodyTemplate, _vectorTable.Name)); + _vectorTable = new Table(_managementConnection, "VectorTestTable", tableDefinition); + _bulkCopySourceTable = new Table(_managementConnection, "VectorBulkCopyTestTable", tableDefinition); + _vectorProcedure = new StoredProcedure(_managementConnection, + prefix: "VectorsAsVarcharSp", + definition: $@" + {VectorParameterName} vector({vectorDimensions}, {TestDataInstance.SqlServerTypeName}), -- Input: Serialized TElement[] as JSON string + {VectorOutputParameterName} vector({vectorDimensions}, {TestDataInstance.SqlServerTypeName}) OUTPUT -- Output: Echoed back from latest inserted row + AS + BEGIN + SET NOCOUNT ON; + + -- Insert into vector table + INSERT INTO {_vectorTable.Name} ({VectorColumnName}) + VALUES ({VectorParameterName}); + + -- Retrieve latest entry (assumes auto-incrementing ID) + SELECT TOP 1 {VectorOutputParameterName} = {VectorColumnName} + FROM {_vectorTable.Name} + ORDER BY Id DESC; + END;"); - _selectCommand = string.Format(s_selectCommandTemplate, _vectorTable.Name); - _insertCommand = string.Format(s_insertCommandTemplate, _vectorTable.Name); + _selectCommand = $"SELECT {VectorColumnName} FROM {_vectorTable.Name} ORDER BY Id DESC"; + _insertCommand = $"INSERT INTO {_vectorTable.Name} ({VectorColumnName}) VALUES ({VectorParameterName})"; } public void Dispose() @@ -134,7 +135,7 @@ public void Dispose() GC.SuppressFinalize(this); } - private static SqlParameter GetParameterByPattern(int pattern, object value) => + private static SqlParameter GetParameterByPattern(int pattern, object? value) => pattern switch { 1 => new SqlParameter @@ -199,7 +200,7 @@ private void ValidateInsertedData(SqlConnection connection, TElement[] expectedD [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public void TestSqlVectorParameterInsertionAndReads( int pattern, - object value, + object? value, TElement[] expectedValues, int expectedLength) { @@ -251,7 +252,7 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, TElement[ [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public async Task TestSqlVectorParameterInsertionAndReadsAsync( int pattern, - object value, + object? value, TElement[] expectedValues, int expectedLength) { @@ -272,7 +273,7 @@ public async Task TestSqlVectorParameterInsertionAndReadsAsync( [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public void TestStoredProcParamsForVector( int pattern, - object value, + object? value, TElement[] expectedValues, int expectedLength) { @@ -315,7 +316,7 @@ public void TestStoredProcParamsForVector( [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] public async Task TestStoredProcParamsForVectorAsync( int pattern, - object value, + object? value, TElement[] expectedValues, int expectedLength) { @@ -364,7 +365,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) sourceConnection.Open(); using SqlConnection destinationConnection = new(_connectionString); destinationConnection.Open(); - DataTable table = null; + DataTable? table = null; switch (bulkCopySourceMode) { @@ -456,7 +457,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) using SqlConnection destinationConnection = new(_connectionString); await destinationConnection.OpenAsync(); - DataTable table = null; + DataTable? table = null; switch (bulkCopySourceMode) { From 662991332e0be8323fcdf40314cc56abc515c46e Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 16:44:35 +0100 Subject: [PATCH 24/26] Comment/documentation updates --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index d0dced4855..7f035026d1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -16,6 +16,10 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest { + /// + /// Base class for all data passed to a type derived from . + /// + /// The element type of the . public abstract class NativeVectorTestDataBase where TElement : unmanaged { @@ -64,6 +68,11 @@ public abstract class NativeVectorTestDataBase ]; } + /// + /// Base class for all strongly-typed manual tests for . + /// + /// The element type of the . + /// The type containing the sample data. public abstract class NativeVectorTestsBase : IDisposable where TElement : unmanaged where TTestData : NativeVectorTestDataBase, new() @@ -135,6 +144,27 @@ public void Dispose() GC.SuppressFinalize(this); } + /// + /// Wraps an inbound in a according to + /// the specified pattern. + /// + /// Pattern number. + /// to wrap. + /// A instance wrapping the . + /// is not a valid pattern. + /// + /// can be a number from 1 to 4, inclusive, with the following meaning: + /// + /// Parameterless constructor, manually setting ParameterName, SqlDbType and Value. + /// Specify the parameter name and value directly, relying upon type inference. + /// Specify the parameter name and SqlDbType, manually setting the Value property. + /// Identical to pattern 3, but with a known-invalid parameter size. + /// + /// + /// + /// + /// + /// private static SqlParameter GetParameterByPattern(int pattern, object? value) => pattern switch { @@ -171,7 +201,7 @@ private void ValidateInsertedData(SqlConnection connection, TElement[] expectedD using SqlDataReader reader = selectCmd.ExecuteReader(); Assert.True(reader.Read(), "No data found in the table."); - //For both null and non-null cases, validate the SqlVector object + // For both null and non-null cases, validate the SqlVector object ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); ValidateSqlVectorObject(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength); ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); @@ -223,7 +253,7 @@ private async Task ValidateInsertedDataAsync(SqlConnection connection, TElement[ using SqlDataReader reader = await selectCmd.ExecuteReaderAsync(); Assert.True(await reader.ReadAsync(), "No data found in the table."); - //For both null and non-null cases, validate the SqlVector object + // For both null and non-null cases, validate the SqlVector object ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); ValidateSqlVectorObject(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength); ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); @@ -360,7 +390,7 @@ public async Task TestStoredProcParamsForVectorAsync( [InlineData(2)] public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) { - //Setup source with test data and create destination table for bulkcopy. + // Setup source with test data and create destination table for bulkcopy. using SqlConnection sourceConnection = new(_connectionString); sourceConnection.Open(); using SqlConnection destinationConnection = new(_connectionString); @@ -371,7 +401,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) case 1: { - // Use SqlServer table as source + // Use SQL Server table as source using SqlCommand insertCmd = new($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); SqlParameter vectorParam = new(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); @@ -396,7 +426,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); } - //Bulkcopy from sql server table to destination table + // Bulk copy from SQL Server table to destination table using SqlCommand sourceDataCommand = new($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); using SqlDataReader reader = sourceDataCommand.ExecuteReader(); @@ -451,7 +481,7 @@ public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) [InlineData(2)] public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) { - //Setup source with test data and create destination table for bulkcopy. + // Setup source with test data and create destination table for bulk copy. using SqlConnection sourceConnection = new(_connectionString); await sourceConnection.OpenAsync(); using SqlConnection destinationConnection = new(_connectionString); @@ -463,7 +493,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) case 1: { - // Use SqlServer table as source + // Use SQL Server table as source using SqlCommand insertCmd = new($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); SqlParameter vectorParam = new(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); @@ -488,7 +518,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); } - //Bulkcopy from sql server table to destination table + // Bulk copy from SQL Server table to destination table using SqlCommand sourceDataCommand = new($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); @@ -502,7 +532,7 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) DestinationTableName = _vectorTable.Name, }; - // Perform bulkcopy + // Perform bulk copy switch (bulkCopySourceMode) { case 1: From 368467b8ecf7c0c845528af0c54399ebca754e12 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 7 Jun 2026 16:49:37 +0100 Subject: [PATCH 25/26] Move Dispose to an inheritance-compatible pattern. --- .../SQL/VectorTest/NativeVectorTestsBase.cs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 7f035026d1..4a3251deed 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -90,6 +90,8 @@ public abstract class NativeVectorTestsBase : IDisposable private readonly string _selectCommand; private readonly string _insertCommand; + private bool _disposed; + // xUnit only allows MemberData for a test to point to static methods, properties and variables. // This presents a problem when the sample data needs to change based upon the element type of // the SqlVector, so this compromises: it instantiates a class derived from NativeVectorTestDataBase, @@ -135,15 +137,31 @@ public NativeVectorTestsBase() public void Dispose() { - using (_managementConnection) - using (_vectorTable) - using (_bulkCopySourceTable) - using (_vectorProcedure) - { } - + Dispose(true); GC.SuppressFinalize(this); } + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _vectorProcedure?.Dispose(); + _bulkCopySourceTable?.Dispose(); + _vectorTable?.Dispose(); + _managementConnection?.Dispose(); + } + + _disposed = true; + } + + ~NativeVectorTestsBase() => + Dispose(false); + /// /// Wraps an inbound in a according to /// the specified pattern. From ded8556713a961fe26434b4d088d94f6f6680ed6 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 8 Jun 2026 19:29:08 +0100 Subject: [PATCH 26/26] Correct post-merge build failure --- .../ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 454c48abb6..5dd71c0f3f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -1,7 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 Xunit; + namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest; #nullable enable