Skip to content

Conversation

@edwardneal
Copy link
Contributor

Description

This deals with a minor point which came out of the original implementation PR: when we build (or read) the byte array representing a vector({size}, float32), we now explicitly do so using BinaryPrimitives' little-endian methods.

There are two slightly unusual points:

  • netfx doesn't have the BinaryPrimitives.WriteSingleLittleEndian method. Instead, we fall back to the existing BitConverterCompatible.SingleToInt32Bits on this target.
  • When building the byte array, we perform two casts: (float)(object)valueSpan[i]. This is another variation of the same pattern used elsewhere of (T)(object)item, and the same pattern holds: the JIT sees that valueSpan is actually a ReadOnlySpan<float> and eliminates the redundant cast. An example of this on Sharplab is here.

Issues

Fixes #3790.

Testing

Automated tests continue to pass.

@edwardneal edwardneal requested a review from a team as a code owner December 24, 2025 00:24
@apoorvdeshmukh
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the SqlVector<T> implementation to use explicit little-endian byte operations when serializing and deserializing vector data. The change replaces platform-specific memory marshaling/copying approaches with consistent loop-based operations using BinaryPrimitives methods to ensure correct endianness.

Key Changes:

  • Replaced manual bit manipulation and platform-specific Buffer.BlockCopy/MemoryMarshal operations with explicit little-endian read/write methods
  • Introduced platform-specific handling: BinaryPrimitives.WriteSingleLittleEndian for .NET and BitConverterCompatible.SingleToInt32Bits + BinaryPrimitives.WriteInt32LittleEndian for .NET Framework
  • Ensured symmetry between serialization (MakeTdsBytes) and deserialization (MakeArray) operations

Comment on lines +172 to +182
if (typeof(T) == typeof(float))
{
Buffer.BlockCopy(values.ToArray(), 0, result, TdsEnums.VECTOR_HEADER_SIZE, values.Length * _elementSize);
}
for (int i = 0, currPosition = TdsEnums.VECTOR_HEADER_SIZE; i < values.Length; i++, currPosition += _elementSize)
{
#if NET
BinaryPrimitives.WriteSingleLittleEndian(result.AsSpan(currPosition), (float)(object)valueSpan[i]);
#else
// Fast span-based copy.
var byteSpan = MemoryMarshal.AsBytes(values.Span);
byteSpan.CopyTo(result.AsSpan(TdsEnums.VECTOR_HEADER_SIZE));
BinaryPrimitives.WriteInt32LittleEndian(result.AsSpan(currPosition), BitConverterCompatible.SingleToInt32Bits((float)(object)valueSpan[i]));
#endif
}
}
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional check for typeof(T) == typeof(float) should include an else clause that throws an exception for unsupported types. Currently, if T is not float, the method would return a byte array with only the header populated and no element data. While GetTypeFieldsOrThrow() prevents non-float types at construction time, adding an explicit else clause here would make the code more defensive and maintainable for future type additions. Consider adding: else { throw SQL.VectorTypeNotSupported(typeof(T).FullName); }

Copilot uses AI. Check for mistakes.
result[i] = (T)(object)BitConverterCompatible.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(_tdsBytes.AsSpan(currPosition)));
#endif
}
}
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional check for typeof(T) == typeof(float) should include an else clause that throws an exception for unsupported types. Currently, if T is not float, the method would return an uninitialized array. While GetTypeFieldsOrThrow() prevents non-float types at construction time, adding an explicit else clause here would make the code more defensive and maintainable for future type additions. Consider adding: else { throw SQL.VectorTypeNotSupported(typeof(T).FullName); }

Suggested change
}
}
else
{
throw SQL.VectorTypeNotSupported(typeof(T).FullName);
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multibyte writes should explicitly happen as little-endian.

2 participants