diff --git a/specifications/bson-binary-vector/tests/README.md b/specifications/bson-binary-vector/tests/README.md
new file mode 100644
index 00000000000..1a2cd87199f
--- /dev/null
+++ b/specifications/bson-binary-vector/tests/README.md
@@ -0,0 +1,61 @@
+# Testing Binary subtype 9: Vector
+
+The JSON files in this directory tree are platform-independent tests that drivers can use to prove their conformance to
+the specification.
+
+These tests focus on the roundtrip of the list of numbers as input/output, along with their data type and byte padding.
+
+Additional tests exist in `bson_corpus/tests/binary.json` but do not sufficiently test the end-to-end process of Vector
+to BSON. For this reason, drivers must create a bespoke test runner for the vector subtype.
+
+## Format
+
+The test data corpus consists of a JSON file for each data type (dtype). Each file contains a number of test cases,
+under the top-level key "tests". Each test case pertains to a single vector. The keys provide the specification of the
+vector. Valid cases also include the Canonical BSON format of a document {test_key: binary}. The "test_key" is common,
+and specified at the top level.
+
+#### Top level keys
+
+Each JSON file contains three top-level keys.
+
+- `description`: human-readable description of what is in the file
+- `test_key`: name used for key when encoding/decoding a BSON document containing the single BSON Binary for the test
+ case. Applies to *every* case.
+- `tests`: array of test case objects, each of which have the following keys. Valid cases will also contain additional
+ binary and json encoding values.
+
+#### Keys of individual tests cases
+
+- `description`: string describing the test.
+- `valid`: boolean indicating if the vector, dtype, and padding should be considered a valid input.
+- `vector`: (required if valid is true) list of numbers
+- `dtype_hex`: string defining the data type in hex (e.g. "0x10", "0x27")
+- `dtype_alias`: (optional) string defining the data dtype, perhaps as Enum.
+- `padding`: (optional) integer for byte padding. Defaults to 0.
+- `canonical_bson`: (required if valid is true) an (uppercase) big-endian hex representation of a BSON byte string.
+
+## Required tests
+
+#### To prove correct in a valid case (`valid: true`), one MUST
+
+- encode a document from the numeric values, dtype, and padding, along with the "test_key", and assert this matches the
+ canonical_bson string.
+- decode the canonical_bson into its binary form, and then assert that the numeric values, dtype, and padding all match
+ those provided in the JSON.
+
+Note: For floating point number types, exact numerical matches may not be possible. Drivers that natively support the
+floating-point type being tested (e.g., when testing float32 vector values in a driver that natively supports float32),
+MUST assert that the input float array is the same after encoding and decoding.
+
+#### To prove correct in an invalid case (`valid:false`), one MUST
+
+- if the vector field is present, raise an exception when attempting to encode a document from the numeric values,
+ dtype, and padding.
+- if the canonical_bson field is present, raise an exception when attempting to deserialize it into the corresponding
+ numeric values, as the field contains corrupted data.
+
+## FAQ
+
+- What MongoDB Server version does this apply to?
+ - Files in the "specifications" repository have no version scheme. They are not tied to a MongoDB server version.
diff --git a/specifications/bson-binary-vector/tests/float32.json b/specifications/bson-binary-vector/tests/float32.json
new file mode 100644
index 00000000000..845f504ff3c
--- /dev/null
+++ b/specifications/bson-binary-vector/tests/float32.json
@@ -0,0 +1,65 @@
+{
+ "description": "Tests of Binary subtype 9, Vectors, with dtype FLOAT32",
+ "test_key": "vector",
+ "tests": [
+ {
+ "description": "Simple Vector FLOAT32",
+ "valid": true,
+ "vector": [127.0, 7.0],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 0,
+ "canonical_bson": "1C00000005766563746F72000A0000000927000000FE420000E04000"
+ },
+ {
+ "description": "Vector with decimals and negative value FLOAT32",
+ "valid": true,
+ "vector": [127.7, -7.7],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 0,
+ "canonical_bson": "1C00000005766563746F72000A0000000927006666FF426666F6C000"
+ },
+ {
+ "description": "Empty Vector FLOAT32",
+ "valid": true,
+ "vector": [],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 0,
+ "canonical_bson": "1400000005766563746F72000200000009270000"
+ },
+ {
+ "description": "Infinity Vector FLOAT32",
+ "valid": true,
+ "vector": ["-inf", 0.0, "inf"],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 0,
+ "canonical_bson": "2000000005766563746F72000E000000092700000080FF000000000000807F00"
+ },
+ {
+ "description": "FLOAT32 with padding",
+ "valid": false,
+ "vector": [127.0, 7.0],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 3,
+ "canonical_bson": "1C00000005766563746F72000A0000000927030000FE420000E04000"
+ },
+ {
+ "description": "Insufficient vector data with 3 bytes FLOAT32",
+ "valid": false,
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "canonical_bson": "1700000005766563746F7200050000000927002A2A2A00"
+ },
+ {
+ "description": "Insufficient vector data with 5 bytes FLOAT32",
+ "valid": false,
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "canonical_bson": "1900000005766563746F7200070000000927002A2A2A2A2A00"
+ }
+ ]
+}
diff --git a/specifications/bson-binary-vector/tests/int8.json b/specifications/bson-binary-vector/tests/int8.json
new file mode 100644
index 00000000000..29524fb6178
--- /dev/null
+++ b/specifications/bson-binary-vector/tests/int8.json
@@ -0,0 +1,57 @@
+{
+ "description": "Tests of Binary subtype 9, Vectors, with dtype INT8",
+ "test_key": "vector",
+ "tests": [
+ {
+ "description": "Simple Vector INT8",
+ "valid": true,
+ "vector": [127, 7],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0,
+ "canonical_bson": "1600000005766563746F7200040000000903007F0700"
+ },
+ {
+ "description": "Empty Vector INT8",
+ "valid": true,
+ "vector": [],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0,
+ "canonical_bson": "1400000005766563746F72000200000009030000"
+ },
+ {
+ "description": "Overflow Vector INT8",
+ "valid": false,
+ "vector": [128],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0
+ },
+ {
+ "description": "Underflow Vector INT8",
+ "valid": false,
+ "vector": [-129],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0
+ },
+ {
+ "description": "INT8 with padding",
+ "valid": false,
+ "vector": [127, 7],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 3,
+ "canonical_bson": "1600000005766563746F7200040000000903037F0700"
+ },
+ {
+ "description": "INT8 with float inputs",
+ "valid": false,
+ "vector": [127.77, 7.77],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0
+ }
+ ]
+}
diff --git a/specifications/bson-binary-vector/tests/packed_bit.json b/specifications/bson-binary-vector/tests/packed_bit.json
new file mode 100644
index 00000000000..a220e7e318e
--- /dev/null
+++ b/specifications/bson-binary-vector/tests/packed_bit.json
@@ -0,0 +1,83 @@
+{
+ "description": "Tests of Binary subtype 9, Vectors, with dtype PACKED_BIT",
+ "test_key": "vector",
+ "tests": [
+ {
+ "description": "Padding specified with no vector data PACKED_BIT",
+ "valid": false,
+ "vector": [],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 1,
+ "canonical_bson": "1400000005766563746F72000200000009100100"
+ },
+ {
+ "description": "Simple Vector PACKED_BIT",
+ "valid": true,
+ "vector": [127, 7],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0,
+ "canonical_bson": "1600000005766563746F7200040000000910007F0700"
+ },
+ {
+ "description": "Empty Vector PACKED_BIT",
+ "valid": true,
+ "vector": [],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0,
+ "canonical_bson": "1400000005766563746F72000200000009100000"
+ },
+ {
+ "description": "PACKED_BIT with padding",
+ "valid": true,
+ "vector": [127, 7],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 3,
+ "canonical_bson": "1600000005766563746F7200040000000910037F0700"
+ },
+ {
+ "description": "Overflow Vector PACKED_BIT",
+ "valid": false,
+ "vector": [256],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0
+ },
+ {
+ "description": "Underflow Vector PACKED_BIT",
+ "valid": false,
+ "vector": [-1],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0
+ },
+ {
+ "description": "Vector with float values PACKED_BIT",
+ "valid": false,
+ "vector": [127.5],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0
+ },
+ {
+ "description": "Exceeding maximum padding PACKED_BIT",
+ "valid": false,
+ "vector": [1],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 8,
+ "canonical_bson": "1500000005766563746F7200030000000910080100"
+ },
+ {
+ "description": "Negative padding PACKED_BIT",
+ "valid": false,
+ "vector": [1],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": -1
+ }
+ ]
+}
diff --git a/specifications/bson-corpus/tests/binary.json b/specifications/bson-corpus/tests/binary.json
index 20aaef743b0..0e0056f3a2c 100644
--- a/specifications/bson-corpus/tests/binary.json
+++ b/specifications/bson-corpus/tests/binary.json
@@ -74,6 +74,36 @@
"description": "$type query operator (conflicts with legacy $binary form with $type field)",
"canonical_bson": "180000000378001000000010247479706500020000000000",
"canonical_extjson": "{\"x\" : { \"$type\" : {\"$numberInt\": \"2\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector FLOAT32",
+ "canonical_bson": "170000000578000A0000000927000000FE420000E04000",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"JwAAAP5CAADgQA==\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector INT8",
+ "canonical_bson": "11000000057800040000000903007F0700",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"AwB/Bw==\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector PACKED_BIT",
+ "canonical_bson": "11000000057800040000000910007F0700",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"EAB/Bw==\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector (Zero-length) FLOAT32",
+ "canonical_bson": "0F0000000578000200000009270000",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"JwA=\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector (Zero-length) INT8",
+ "canonical_bson": "0F0000000578000200000009030000",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"AwA=\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector (Zero-length) PACKED_BIT",
+ "canonical_bson": "0F0000000578000200000009100000",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"EAA=\", \"subType\": \"09\"}}}"
}
],
"decodeErrors": [
diff --git a/src/MongoDB.Bson/ObjectModel/BinaryVector.cs b/src/MongoDB.Bson/ObjectModel/BinaryVector.cs
new file mode 100644
index 00000000000..52e804bf433
--- /dev/null
+++ b/src/MongoDB.Bson/ObjectModel/BinaryVector.cs
@@ -0,0 +1,104 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+
+namespace MongoDB.Bson
+{
+ ///
+ /// Represents a binary vector.
+ ///
+ public abstract class BinaryVector
+ where TItem : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The vector data.
+ /// Type of the vector data.
+ private protected BinaryVector(ReadOnlyMemory data, BinaryVectorDataType dataType)
+ {
+ DataType = dataType;
+ Data = data;
+ }
+
+ ///
+ /// Gets the vector data type.
+ ///
+ public BinaryVectorDataType DataType { get; }
+
+ ///
+ /// Gets the vector data.
+ ///
+ public ReadOnlyMemory Data { get; }
+ }
+
+ ///
+ /// Represents a vector of values.
+ ///
+ public sealed class BinaryVectorFloat32 : BinaryVector
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BinaryVectorFloat32(ReadOnlyMemory data) : base(data, BinaryVectorDataType.Float32)
+ {
+ }
+ }
+
+ ///
+ /// Represents a vector of values.
+ ///
+ [CLSCompliant(false)]
+ public sealed class BinaryVectorInt8 : BinaryVector
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BinaryVectorInt8(ReadOnlyMemory data) : base(data, BinaryVectorDataType.Int8)
+ {
+ }
+ }
+
+ ///
+ /// Represents a vector of 0/1 values.
+ /// The vector values are packed into groups of 8 (a byte).
+ ///
+ public sealed class BinaryVectorPackedBit : BinaryVector
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BinaryVectorPackedBit(ReadOnlyMemory data, byte padding) : base(data, BinaryVectorDataType.PackedBit)
+ {
+ if (padding > 7)
+ {
+ throw new ArgumentOutOfRangeException(nameof(padding), padding, "Padding is expected to be in the range of [0..7].");
+ }
+
+ if (padding > 0 && data.Length == 0)
+ {
+ throw new ArgumentException("Can't specify non zero padding with no data.");
+ }
+
+ Padding = padding;
+ }
+
+ ///
+ /// Gets the bits padding.
+ ///
+ public byte Padding { get; }
+ }
+}
diff --git a/src/MongoDB.Bson/ObjectModel/BinaryVectorDataType.cs b/src/MongoDB.Bson/ObjectModel/BinaryVectorDataType.cs
new file mode 100644
index 00000000000..4e1f8341f85
--- /dev/null
+++ b/src/MongoDB.Bson/ObjectModel/BinaryVectorDataType.cs
@@ -0,0 +1,39 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+namespace MongoDB.Bson
+{
+ ///
+ /// Represents the data type of a binary Vector.
+ ///
+ ///
+ public enum BinaryVectorDataType
+ {
+ ///
+ /// Data type is float32.
+ ///
+ Float32 = 0x27,
+
+ ///
+ /// Data type is int8.
+ ///
+ Int8 = 0x03,
+
+ ///
+ /// Data type is packed bit.
+ ///
+ PackedBit = 0x10
+ }
+}
diff --git a/src/MongoDB.Bson/ObjectModel/BsonBinarySubType.cs b/src/MongoDB.Bson/ObjectModel/BsonBinarySubType.cs
index 6fbde665ee4..67c019b5881 100644
--- a/src/MongoDB.Bson/ObjectModel/BsonBinarySubType.cs
+++ b/src/MongoDB.Bson/ObjectModel/BsonBinarySubType.cs
@@ -60,6 +60,10 @@ public enum BsonBinarySubType
///
Sensitive = 0x08,
///
+ /// Vector data.
+ ///
+ Vector = 0x09,
+ ///
/// User defined binary data.
///
UserDefined = 0x80
diff --git a/src/MongoDB.Bson/Serialization/Attributes/BinaryVectorAttribute.cs b/src/MongoDB.Bson/Serialization/Attributes/BinaryVectorAttribute.cs
new file mode 100644
index 00000000000..78a117bfd1a
--- /dev/null
+++ b/src/MongoDB.Bson/Serialization/Attributes/BinaryVectorAttribute.cs
@@ -0,0 +1,53 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Serializers;
+
+namespace MongoDB.Bson
+{
+ ///
+ /// Sets the representation for this field or property as a binary vector with the specified data type.
+ ///
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
+ public sealed class BinaryVectorAttribute : Attribute, IBsonMemberMapAttribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BinaryVectorAttribute(BinaryVectorDataType dataType)
+ {
+ DataType = dataType;
+ }
+
+ ///
+ /// Gets the vector data type.
+ ///
+ public BinaryVectorDataType DataType { get; init; }
+
+ ///
+ /// Applies the attribute to the member map.
+ ///
+ /// The member map.
+ public void Apply(BsonMemberMap memberMap)
+ {
+ var serializer = CreateSerializer(memberMap.MemberType);
+ memberMap.SetSerializer(serializer);
+ }
+
+ private IBsonSerializer CreateSerializer(Type type) => BinaryVectorSerializer.CreateSerializer(type, DataType);
+ }
+}
diff --git a/src/MongoDB.Bson/Serialization/BinaryVectorExtensions.cs b/src/MongoDB.Bson/Serialization/BinaryVectorExtensions.cs
new file mode 100644
index 00000000000..8dd2ae4ca77
--- /dev/null
+++ b/src/MongoDB.Bson/Serialization/BinaryVectorExtensions.cs
@@ -0,0 +1,38 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+namespace MongoDB.Bson.Serialization
+{
+ ///
+ /// Contains extensions methods for
+ ///
+ public static class BinaryVectorExtensions
+ {
+ ///
+ /// Converts to .
+ ///
+ /// The .NET data type.
+ /// The binary vector.
+ /// A instance.
+ public static BsonBinaryData ToBsonBinaryData(this BinaryVector binaryVector)
+ where TItem : struct
+ {
+ var bytes = BinaryVectorWriter.WriteToBytes(binaryVector);
+ var binaryData = new BsonBinaryData(bytes, BsonBinarySubType.Vector);
+
+ return binaryData;
+ }
+ }
+}
diff --git a/src/MongoDB.Bson/Serialization/BinaryVectorReader.cs b/src/MongoDB.Bson/Serialization/BinaryVectorReader.cs
new file mode 100644
index 00000000000..ef83c201091
--- /dev/null
+++ b/src/MongoDB.Bson/Serialization/BinaryVectorReader.cs
@@ -0,0 +1,151 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace MongoDB.Bson.Serialization
+{
+ internal static class BinaryVectorReader
+ {
+ public static BinaryVector ReadBinaryVector(ReadOnlyMemory vectorData)
+ where TItem : struct
+ {
+ var (items, padding, vectorDataType) = ReadBinaryVectorAsArray(vectorData);
+
+ return CreateBinaryVector(items, padding, vectorDataType);
+ }
+
+ public static (TItem[] Items, byte Padding, BinaryVectorDataType VectorDataType) ReadBinaryVectorAsArray(ReadOnlyMemory vectorData)
+ where TItem : struct
+ {
+ var (vectorDataBytes, padding, vectorDataType) = ReadBinaryVectorAsBytes(vectorData);
+ ValidateItemType(vectorDataType);
+
+ TItem[] items;
+
+ switch (vectorDataType)
+ {
+ case BinaryVectorDataType.Float32:
+
+ if ((vectorDataBytes.Span.Length & 3) != 0)
+ {
+ throw new FormatException("Data length of binary vector of type Float32 must be a multiple of 4 bytes.");
+ }
+
+ if (BitConverter.IsLittleEndian)
+ {
+ var singles = MemoryMarshal.Cast(vectorDataBytes.Span);
+ items = (TItem[])(object)singles.ToArray();
+ }
+ else
+ {
+ throw new NotSupportedException("Binary vector data is not supported on Big Endian architecture yet.");
+ }
+ break;
+ case BinaryVectorDataType.Int8:
+ var itemsSpan = MemoryMarshal.Cast(vectorDataBytes.Span);
+ items = (TItem[])(object)itemsSpan.ToArray();
+ break;
+ case BinaryVectorDataType.PackedBit:
+ items = (TItem[])(object)vectorDataBytes.ToArray();
+ break;
+ default:
+ throw new NotSupportedException($"Binary vector data type {vectorDataType} is not supported.");
+ }
+
+ return (items, padding, vectorDataType);
+ }
+
+ public static (ReadOnlyMemory Bytes, byte Padding, BinaryVectorDataType VectorDataType) ReadBinaryVectorAsBytes(ReadOnlyMemory vectorData)
+ {
+ if (vectorData.Length < 2)
+ {
+ throw new ArgumentException($"Invalid {nameof(vectorData)} size {vectorData.Length}.", nameof(vectorData));
+ }
+
+ var vectorDataSpan = vectorData.Span;
+ var vectorDataType = (BinaryVectorDataType)vectorDataSpan[0];
+
+ var padding = vectorDataSpan[1];
+ if (padding > 7)
+ {
+ throw new FormatException($"Invalid padding size {padding}.");
+ }
+
+ if (padding != 0 && vectorData.Length == 2)
+ {
+ throw new FormatException($"Vector data elements expected with padding size {padding}, but no elements found.");
+ }
+
+ return (vectorData.Slice(2), padding, vectorDataType);
+ }
+
+ private static BinaryVector CreateBinaryVector(TItem[] items, byte padding, BinaryVectorDataType vectorDataType)
+ where TItem : struct
+ {
+ switch (vectorDataType)
+ {
+ case BinaryVectorDataType.Float32:
+ ValidateItemTypeForBinaryVector();
+ return new BinaryVectorFloat32(AsTypedArrayOrThrow()) as BinaryVector;
+ case BinaryVectorDataType.Int8:
+ ValidateItemTypeForBinaryVector();
+ return new BinaryVectorInt8(AsTypedArrayOrThrow()) as BinaryVector;
+ case BinaryVectorDataType.PackedBit:
+ ValidateItemTypeForBinaryVector();
+ return new BinaryVectorPackedBit(AsTypedArrayOrThrow(), padding) as BinaryVector;
+ default:
+ throw new NotSupportedException($"Vector data type {vectorDataType} is not supported.");
+ }
+
+ TExpectedItem[] AsTypedArrayOrThrow()
+ {
+ if (items is not TExpectedItem[] result)
+ {
+ throw new ArgumentException($"Item type {typeof(TItem)} is not supported with {vectorDataType} vector type, expected {typeof(TExpectedItem)}.");
+ }
+
+ return result;
+ }
+ }
+
+ public static void ValidateItemType(BinaryVectorDataType binaryVectorDataType)
+ {
+ IEnumerable expectedItemTypes = binaryVectorDataType switch
+ {
+ BinaryVectorDataType.Float32 => [typeof(float)],
+ BinaryVectorDataType.Int8 => [typeof(byte), typeof(sbyte)],
+ BinaryVectorDataType.PackedBit => [typeof(byte)],
+ _ => throw new ArgumentException(nameof(binaryVectorDataType), "Unsupported vector datatype.")
+ };
+
+ if (!expectedItemTypes.Contains(typeof(TItem)))
+ {
+ throw new NotSupportedException($"Item type {typeof(TItem)} is not supported with {binaryVectorDataType} vector type, expected item type to be {string.Join(",", expectedItemTypes)}.");
+ }
+ }
+
+ private static void ValidateItemTypeForBinaryVector()
+ {
+ if (typeof(TItem) != typeof(TItemExpectedType))
+ {
+ throw new NotSupportedException($"Expected {typeof(TItemExpectedType)} for {typeof(TBinaryVectorType)}, but found {typeof(TItem)}.");
+ }
+ }
+ }
+}
diff --git a/src/MongoDB.Bson/Serialization/BinaryVectorWriter.cs b/src/MongoDB.Bson/Serialization/BinaryVectorWriter.cs
new file mode 100644
index 00000000000..0e9d5e74f6d
--- /dev/null
+++ b/src/MongoDB.Bson/Serialization/BinaryVectorWriter.cs
@@ -0,0 +1,49 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace MongoDB.Bson.Serialization
+{
+ internal static class BinaryVectorWriter
+ {
+ public static byte[] WriteToBytes(BinaryVector binaryVector)
+ where TItem : struct
+ {
+ byte padding = 0;
+ if (binaryVector is BinaryVectorPackedBit binaryVectorPackedBit)
+ {
+ padding = binaryVectorPackedBit.Padding;
+ }
+
+ return WriteToBytes(binaryVector.Data.Span, binaryVector.DataType, padding);
+ }
+
+ public static byte[] WriteToBytes(ReadOnlySpan vectorData, BinaryVectorDataType binaryVectorDataType, byte padding)
+ where TItem : struct
+ {
+ if (!BitConverter.IsLittleEndian)
+ {
+ throw new NotSupportedException("Binary vector data is not supported on Big Endian architecture yet.");
+ }
+
+ var vectorDataBytes = MemoryMarshal.Cast(vectorData);
+ byte[] result = [(byte)binaryVectorDataType, padding, .. vectorDataBytes];
+
+ return result;
+ }
+ }
+}
diff --git a/src/MongoDB.Bson/Serialization/BsonBinaryDataExtensions.cs b/src/MongoDB.Bson/Serialization/BsonBinaryDataExtensions.cs
new file mode 100644
index 00000000000..d6915c40485
--- /dev/null
+++ b/src/MongoDB.Bson/Serialization/BsonBinaryDataExtensions.cs
@@ -0,0 +1,74 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+
+namespace MongoDB.Bson.Serialization
+{
+ ///
+ /// Contains extensions methods for .
+ ///
+ public static class BsonBinaryDataExtensions
+ {
+ ///
+ /// Converts to .
+ ///
+ /// Data type of the binary vector.
+ /// The binary data.
+ /// A instance.
+ public static BinaryVector ToBinaryVector(this BsonBinaryData binaryData)
+ where TItem : struct
+ {
+ EnsureBinaryVectorSubType(binaryData);
+
+ return BinaryVectorReader.ReadBinaryVector(binaryData.Bytes);
+ }
+
+ ///
+ /// Extracts binary vector data from as bytes, padding and data type.
+ /// The result bytes should be interpreted according to the vector data type.
+ ///
+ /// The binary data.
+ /// Vector bytes, padding and datatype
+ public static (ReadOnlyMemory Bytes, byte Padding, BinaryVectorDataType VectorDataType) ToBinaryVectorAsBytes(this BsonBinaryData binaryData)
+ {
+ EnsureBinaryVectorSubType(binaryData);
+
+ return BinaryVectorReader.ReadBinaryVectorAsBytes(binaryData.Bytes);
+ }
+
+ ///
+ /// Extracts binary vector data from as an array of , padding and data type.
+ /// The result bytes should be interpreted according to the vector data type.
+ ///
+ /// The binary data.
+ /// Vector data, padding and datatype
+ public static (TItem[] Items, byte Padding, BinaryVectorDataType VectorDataType) ToBinaryVectorAsArray(this BsonBinaryData binaryData)
+ where TItem : struct
+ {
+ EnsureBinaryVectorSubType(binaryData);
+
+ return BinaryVectorReader.ReadBinaryVectorAsArray(binaryData.Bytes);
+ }
+
+ private static void EnsureBinaryVectorSubType(BsonBinaryData binaryData)
+ {
+ if (binaryData.SubType != BsonBinarySubType.Vector)
+ {
+ throw new InvalidOperationException($"Expected BsonBinary Vector subtype, but found {binaryData.SubType} instead.");
+ }
+ }
+ }
+}
diff --git a/src/MongoDB.Bson/Serialization/BsonObjectModelSerializationProvider.cs b/src/MongoDB.Bson/Serialization/BsonObjectModelSerializationProvider.cs
index 52521e461f8..bb8347f8879 100644
--- a/src/MongoDB.Bson/Serialization/BsonObjectModelSerializationProvider.cs
+++ b/src/MongoDB.Bson/Serialization/BsonObjectModelSerializationProvider.cs
@@ -52,7 +52,10 @@ static BsonObjectModelSerializationProvider()
{ typeof(BsonSymbol), BsonSymbolSerializer.Instance },
{ typeof(BsonTimestamp), BsonTimestampSerializer.Instance },
{ typeof(BsonUndefined), BsonUndefinedSerializer.Instance },
- { typeof(BsonValue), BsonValueSerializer.Instance }
+ { typeof(BsonValue), BsonValueSerializer.Instance },
+ { typeof(BinaryVectorFloat32), BinaryVectorSerializer.BinaryVectorFloat32Serializer },
+ { typeof(BinaryVectorInt8), BinaryVectorSerializer.BinaryVectorInt8Serializer },
+ { typeof(BinaryVectorPackedBit), BinaryVectorSerializer.BinaryVectorPackedBitSerializer },
};
}
diff --git a/src/MongoDB.Bson/Serialization/Serializers/BinaryVectorSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/BinaryVectorSerializer.cs
new file mode 100644
index 00000000000..cd97cfd21e7
--- /dev/null
+++ b/src/MongoDB.Bson/Serialization/Serializers/BinaryVectorSerializer.cs
@@ -0,0 +1,274 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using MongoDB.Bson.IO;
+
+namespace MongoDB.Bson.Serialization.Serializers
+{
+ internal static class BinaryVectorSerializer
+ {
+ public static BinaryVectorSerializer BinaryVectorFloat32Serializer { get; } = new BinaryVectorSerializer(BinaryVectorDataType.Float32);
+ public static BinaryVectorSerializer BinaryVectorInt8Serializer { get; } = new BinaryVectorSerializer(BinaryVectorDataType.Int8);
+ public static BinaryVectorSerializer BinaryVectorPackedBitSerializer { get; } = new BinaryVectorSerializer(BinaryVectorDataType.PackedBit);
+
+ public static IBsonSerializer CreateArraySerializer(Type itemType, BinaryVectorDataType binaryVectorDataType) =>
+ CreateSerializerInstance(typeof(ArrayAsBinaryVectorSerializer<>).MakeGenericType(itemType), binaryVectorDataType);
+
+ public static IBsonSerializer CreateBinaryVectorSerializer(Type binaryVectorType, Type itemType, BinaryVectorDataType binaryVectorDataType) =>
+ CreateSerializerInstance(typeof(BinaryVectorSerializer<,>).MakeGenericType(binaryVectorType, itemType), binaryVectorDataType);
+
+ public static IBsonSerializer CreateMemorySerializer(Type itemType, BinaryVectorDataType binaryVectorDataType) =>
+ CreateSerializerInstance(typeof(MemoryAsBinaryVectorSerializer<>).MakeGenericType(itemType), binaryVectorDataType);
+
+ public static IBsonSerializer CreateReadonlyMemorySerializer(Type itemType, BinaryVectorDataType binaryVectorDataType) =>
+ CreateSerializerInstance(typeof(ReadOnlyMemoryAsBinaryVectorSerializer<>).MakeGenericType(itemType), binaryVectorDataType);
+
+ public static IBsonSerializer CreateSerializer(Type type, BinaryVectorDataType binaryVectorDataType)
+ {
+ // Arrays
+ if (type.IsArray)
+ {
+ var itemType = type.GetElementType();
+ return CreateArraySerializer(itemType, binaryVectorDataType);
+ }
+
+ // BinaryVector
+ if (type == typeof(BinaryVectorFloat32) ||
+ type == typeof(BinaryVectorInt8) ||
+ type == typeof(BinaryVectorPackedBit))
+ {
+ return CreateBinaryVectorSerializer(type, GetItemType(type.BaseType), binaryVectorDataType);
+ }
+
+ // Memory/ReadonlyMemory
+ var genericTypeDefinition = type.IsGenericType ? type.GetGenericTypeDefinition() : null;
+ if (genericTypeDefinition == typeof(Memory<>))
+ {
+ return CreateMemorySerializer(GetItemType(type), binaryVectorDataType);
+ }
+ else if (genericTypeDefinition == typeof(ReadOnlyMemory<>))
+ {
+ return CreateReadonlyMemorySerializer(GetItemType(type), binaryVectorDataType);
+ }
+
+ throw new NotSupportedException($"Type {type} cannot be serialized as a binary vector.");
+
+ Type GetItemType(Type containerType)
+ {
+ var genericArguments = containerType.GetGenericArguments();
+ if (genericArguments.Length != 1)
+ {
+ throw new NotSupportedException($"Type {type} cannot be serialized as a binary vector.");
+ }
+
+ return genericArguments[0];
+ }
+ }
+
+ private static IBsonSerializer CreateSerializerInstance(Type serializerType, BinaryVectorDataType binaryVectorDataType) =>
+ (IBsonSerializer)Activator.CreateInstance(serializerType, binaryVectorDataType);
+ }
+
+ ///
+ /// Represents a serializer for TItemContainer values represented as a BinaryVector.
+ ///
+ /// The items container, for example or .
+ /// The .NET data type.
+ public abstract class BinaryVectorSerializerBase : SerializerBase
+ where TItem : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Type of the binary vector data.
+ private protected BinaryVectorSerializerBase(BinaryVectorDataType binaryVectorDataType)
+ {
+ BinaryVectorReader.ValidateItemType(binaryVectorDataType);
+
+ VectorDataType = binaryVectorDataType;
+ }
+
+ ///
+ /// Gets the type of the vector data.
+ ///
+ public BinaryVectorDataType VectorDataType { get; }
+
+ ///
+ public override int GetHashCode() => 0;
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (object.ReferenceEquals(obj, null)) { return false; }
+ if (object.ReferenceEquals(this, obj)) { return true; }
+ return
+ base.Equals(obj) &&
+ obj is BinaryVectorSerializerBase other &&
+ object.Equals(VectorDataType, other.VectorDataType);
+ }
+
+ ///
+ /// Reads bson binary data.
+ ///
+ /// The bson reader.
+ protected BsonBinaryData ReadAndValidateBsonBinaryData(IBsonReader bsonReader)
+ {
+ var bsonType = bsonReader.GetCurrentBsonType();
+ if (bsonType != BsonType.Binary)
+ {
+ throw CreateCannotDeserializeFromBsonTypeException(bsonType);
+ }
+
+ var binaryData = bsonReader.ReadBinaryData();
+
+ return binaryData;
+ }
+ }
+
+ ///
+ /// Represents a serializer for .
+ ///
+ /// The concrete type derived from .
+ /// The .NET data type.
+ public sealed class BinaryVectorSerializer : BinaryVectorSerializerBase
+ where TItemContainer : BinaryVector
+ where TItem : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BinaryVectorSerializer(BinaryVectorDataType binaryVectorDataType) :
+ base(binaryVectorDataType)
+ {
+ }
+
+ ///
+ public override TItemContainer Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
+ {
+ var binaryData = ReadAndValidateBsonBinaryData(context.Reader);
+ return (TItemContainer)binaryData.ToBinaryVector();
+ }
+
+ ///
+ public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TItemContainer value)
+ {
+ var binaryData = value.ToBsonBinaryData();
+
+ context.Writer.WriteBinaryData(binaryData);
+ }
+ }
+
+ ///
+ /// A base class for serializers for containers represented as a BinaryVector.
+ ///
+ /// The collection type.
+ /// The .NET data type.
+ public abstract class ItemContainerAsBinaryVectorSerializer : BinaryVectorSerializerBase
+ where TItem : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ItemContainerAsBinaryVectorSerializer(BinaryVectorDataType binaryVectorDataType) :
+ base(binaryVectorDataType)
+ {
+ }
+
+ ///
+ public sealed override TItemContainer Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
+ {
+ var binaryData = ReadAndValidateBsonBinaryData(context.Reader);
+ var (items, padding, _) = binaryData.ToBinaryVectorAsArray();
+
+ if (padding != 0)
+ {
+ throw new FormatException($"Non-zero padding is supported only in {nameof(BinaryVectorPackedBit)} data type.");
+ }
+
+ return CreateResult(items);
+ }
+
+ ///
+ public sealed override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TItemContainer value)
+ {
+ var vectorData = GetItemsSpan(value);
+ var bytes = BinaryVectorWriter.WriteToBytes(vectorData, VectorDataType, 0);
+ var binaryData = new BsonBinaryData(bytes, BsonBinarySubType.Vector);
+
+ context.Writer.WriteBinaryData(binaryData);
+ }
+
+ private protected abstract TItemContainer CreateResult(TItem[] items);
+ private protected abstract ReadOnlySpan GetItemsSpan(TItemContainer data);
+ }
+
+ ///
+ /// Represents a serializer for arrays represented as a BinaryVector.
+ ///
+ /// The .NET data type.
+ public sealed class ArrayAsBinaryVectorSerializer : ItemContainerAsBinaryVectorSerializer
+ where TItem : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ArrayAsBinaryVectorSerializer(BinaryVectorDataType binaryVectorDataType) : base(binaryVectorDataType)
+ {
+ }
+
+ private protected override ReadOnlySpan GetItemsSpan(TItem[] data) => data;
+
+ private protected override TItem[] CreateResult(TItem[] items) => items;
+ }
+
+ ///
+ /// Represents a serializer for represented as a binary vector.
+ ///
+ /// The .NET data type.
+ public sealed class MemoryAsBinaryVectorSerializer : ItemContainerAsBinaryVectorSerializer, TItem>
+ where TItem : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MemoryAsBinaryVectorSerializer(BinaryVectorDataType binaryVectorDataType) : base(binaryVectorDataType)
+ {
+ }
+
+ private protected override ReadOnlySpan GetItemsSpan(Memory data) => data.Span;
+
+ private protected override Memory CreateResult(TItem[] items) => new(items);
+ }
+
+ ///
+ /// Represents a serializer for represented as a BinaryVector.
+ ///
+ /// The .NET data type.
+ public sealed class ReadOnlyMemoryAsBinaryVectorSerializer : ItemContainerAsBinaryVectorSerializer, TItem>
+ where TItem : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ReadOnlyMemoryAsBinaryVectorSerializer(BinaryVectorDataType binaryVectorDataType) : base(binaryVectorDataType)
+ {
+ }
+
+ private protected override ReadOnlySpan GetItemsSpan(ReadOnlyMemory data) => data.Span;
+
+ private protected override ReadOnlyMemory CreateResult(TItem[] items) => new(items);
+ }
+}
diff --git a/src/MongoDB.Driver/BinaryVectorExtensions.cs b/src/MongoDB.Driver/BinaryVectorExtensions.cs
new file mode 100644
index 00000000000..93b648a3ec9
--- /dev/null
+++ b/src/MongoDB.Driver/BinaryVectorExtensions.cs
@@ -0,0 +1,43 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+
+namespace MongoDB.Driver
+{
+ ///
+ /// Contains extensions methods for
+ ///
+ public static class BinaryVectorDriverExtensions
+ {
+ ///
+ /// Converts to .
+ ///
+ ///
+ /// The binary vector.
+ /// A instance.
+ public static QueryVector ToQueryVector(this BinaryVector binaryVector)
+ where TItem : struct =>
+ binaryVector switch
+ {
+ BinaryVectorFloat32 binaryVectorFloat32 => new(binaryVectorFloat32.ToBsonBinaryData()),
+ BinaryVectorInt8 binaryVectorInt8 => new(binaryVectorInt8.ToBsonBinaryData()),
+ BinaryVectorPackedBit binaryVectorPackedBit => new(binaryVectorPackedBit.ToBsonBinaryData()),
+ _ => throw new InvalidOperationException($"Invalid binary vector type {binaryVector?.GetType()}")
+ };
+ }
+}
diff --git a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
index 90c3c1f84e7..53f6017f6e8 100644
--- a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
+++ b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
@@ -1996,7 +1996,7 @@ public static PipelineStageDefinition VectorSearch(
ClientSideProjectionHelper.ThrowIfClientSideProjection(args.DocumentSerializer, operatorName);
var vectorSearchOperator = new BsonDocument
{
- { "queryVector", queryVector.Array },
+ { "queryVector", queryVector.Vector },
{ "path", field.Render(args).FieldName },
{ "limit", limit },
{ "numCandidates", options?.NumberOfCandidates ?? limit * 10, options?.Exact != true },
diff --git a/src/MongoDB.Driver/QueryVector.cs b/src/MongoDB.Driver/QueryVector.cs
index 2af2615826a..5578d92d6b6 100644
--- a/src/MongoDB.Driver/QueryVector.cs
+++ b/src/MongoDB.Driver/QueryVector.cs
@@ -29,14 +29,27 @@ namespace MongoDB.Driver
public sealed class QueryVector
{
///
- /// Gets the underlying BSON array.
+ /// Gets the underlying bson value representing the vector.
+ /// Possible values are or encoding
///
- internal BsonArray Array { get; } // do not make public because in the case of ReadOnlyMemory the BsonArray subclass is not fully functional
+ internal BsonValue Vector { get; } // do not make public because in the case of ReadOnlyMemory the BsonArray subclass is not fully functional
private QueryVector(BsonArray array)
{
Ensure.IsNotNullOrEmpty(array, nameof(array));
- Array = array;
+ Vector = array;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The bson binary data.
+ public QueryVector(BsonBinaryData bsonBinaryData)
+ {
+ Ensure.IsNotNull(bsonBinaryData, nameof(bsonBinaryData));
+ Ensure.IsEqualTo(bsonBinaryData.SubType, BsonBinarySubType.Vector, nameof(bsonBinaryData.SubType));
+
+ Vector = bsonBinaryData;
}
///
@@ -94,7 +107,7 @@ public QueryVector(ReadOnlyMemory readOnlyMemory) :
public static implicit operator QueryVector(float[] array) => new(array);
///
- /// Performs an implicit conversion from a of to .
+ /// Performs an implicit conversion from a of to .
///
/// The readOnlyMemory.
///
@@ -112,13 +125,41 @@ public QueryVector(ReadOnlyMemory readOnlyMemory) :
public static implicit operator QueryVector(int[] array) => new(array);
///
- /// Performs an implicit conversion from a of to .
+ /// Performs an implicit conversion from a of to .
///
/// The readOnlyMemory.
///
/// The result of the conversion.
///
public static implicit operator QueryVector(ReadOnlyMemory readOnlyMemory) => new(readOnlyMemory);
+
+ ///
+ /// Performs an implicit conversion from a of to .
+ ///
+ /// The binary vector int8.
+ ///
+ /// The result of the conversion.
+ ///
+ [CLSCompliant(false)]
+ public static implicit operator QueryVector(BinaryVectorInt8 binaryVectorInt8) => binaryVectorInt8.ToQueryVector();
+
+ ///
+ /// Performs an implicit conversion from a of to .
+ ///
+ /// The binary vector float32.
+ ///
+ /// The result of the conversion.
+ ///
+ public static implicit operator QueryVector(BinaryVectorFloat32 binaryVectorFloat32) => binaryVectorFloat32.ToQueryVector();
+
+ ///
+ /// Performs an implicit conversion from a of to .
+ ///
+ /// The binary vector packed bit.
+ ///
+ /// The result of the conversion.
+ ///
+ public static implicit operator QueryVector(BinaryVectorPackedBit binaryVectorPackedBit) => binaryVectorPackedBit.ToQueryVector();
}
// WARNING: this class is a very partial implementation of a BsonArray subclass
diff --git a/tests/MongoDB.Bson.Tests/Serialization/Attributes/BinaryVectorAttributeTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Attributes/BinaryVectorAttributeTests.cs
new file mode 100644
index 00000000000..83564d51c94
--- /dev/null
+++ b/tests/MongoDB.Bson.Tests/Serialization/Attributes/BinaryVectorAttributeTests.cs
@@ -0,0 +1,181 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using FluentAssertions;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Serializers;
+using Xunit;
+
+namespace MongoDB.Bson.Tests.Serialization.Attributes
+{
+ public class BinaryVectorAttributeTests
+ {
+ [Fact]
+ public void BinaryVectorAttribute_should_set_binaryVectorArraySerializer_for_array()
+ {
+ var classMap = BsonClassMap.LookupClassMap(typeof(ArrayHolder));
+
+ AssertSerializer, byte[], byte>(classMap, nameof(ArrayHolder.ValuesByte), BinaryVectorDataType.Int8);
+ AssertSerializer, sbyte[], sbyte>(classMap, nameof(ArrayHolder.ValuesSByte), BinaryVectorDataType.Int8);
+ AssertSerializer, byte[], byte>(classMap, nameof(ArrayHolder.ValuesPackedBit), BinaryVectorDataType.PackedBit);
+ AssertSerializer, float[], float>(classMap, nameof(ArrayHolder.ValuesFloat), BinaryVectorDataType.Float32);
+ }
+
+ [Fact]
+ public void BinaryVectorAttribute_should_set_binaryVectorSerializer_for_binaryVector()
+ {
+ var classMap = BsonClassMap.LookupClassMap(typeof(BinaryVectorHolder));
+
+ AssertSerializer, BinaryVectorInt8, sbyte>(classMap, nameof(BinaryVectorHolder.ValuesInt8), BinaryVectorDataType.Int8);
+ AssertSerializer, BinaryVectorPackedBit, byte>(classMap, nameof(BinaryVectorHolder.ValuesPackedBit), BinaryVectorDataType.PackedBit);
+ AssertSerializer, BinaryVectorFloat32, float>(classMap, nameof(BinaryVectorHolder.ValuesFloat), BinaryVectorDataType.Float32);
+ }
+
+ [Fact]
+ public void BinaryVectorAttribute_should_set_binaryVectorMemorySerializer_for_memory()
+ {
+ var classMap = BsonClassMap.LookupClassMap(typeof(MemoryHolder));
+
+ AssertSerializer, Memory, byte>(classMap, nameof(MemoryHolder.ValuesByte), BinaryVectorDataType.Int8);
+ AssertSerializer, Memory, sbyte>(classMap, nameof(MemoryHolder.ValuesSByte), BinaryVectorDataType.Int8);
+ AssertSerializer, Memory, byte>(classMap, nameof(MemoryHolder.ValuesPackedBit), BinaryVectorDataType.PackedBit);
+ AssertSerializer, Memory, float>(classMap, nameof(MemoryHolder.ValuesFloat), BinaryVectorDataType.Float32);
+ }
+
+ [Fact]
+ public void BinaryVectorAttribute_should_set_binaryVectorReadonlyMemorySerializer_for_readOnlyMemory()
+ {
+ var classMap = BsonClassMap.LookupClassMap(typeof(ReadOnlyMemoryHolder));
+
+ AssertSerializer, ReadOnlyMemory, byte>(classMap, nameof(ReadOnlyMemoryHolder.ValuesByte), BinaryVectorDataType.Int8);
+ AssertSerializer, ReadOnlyMemory, sbyte>(classMap, nameof(ReadOnlyMemoryHolder.ValuesSByte), BinaryVectorDataType.Int8);
+ AssertSerializer, ReadOnlyMemory, byte>(classMap, nameof(ReadOnlyMemoryHolder.ValuesPackedBit), BinaryVectorDataType.PackedBit);
+ AssertSerializer, ReadOnlyMemory, float>(classMap, nameof(ReadOnlyMemoryHolder.ValuesFloat), BinaryVectorDataType.Float32);
+ }
+
+ [Theory]
+ [InlineData(nameof(InvalidTypesHolder.List))]
+ [InlineData(nameof(InvalidTypesHolder.Dictionary))]
+ public void BinaryVectorAttribute_should_throw_on_invalid_type(string memberName)
+ {
+ var memberInfo = typeof(InvalidTypesHolder).GetProperty(memberName);
+ var classMap = new BsonClassMap(cm => cm.MapMember(memberInfo));
+
+ var exception = Record.Exception(() => classMap.AutoMap());
+
+ exception.Should().BeOfType();
+ exception.Message.Should().Be($"Type {memberInfo.PropertyType} cannot be serialized as a binary vector.");
+ }
+
+ [Fact]
+ public void BinaryVectorSerializer_should_be_used_for_binaryvector_without_attribute()
+ {
+ var classMap = BsonClassMap.LookupClassMap(typeof(BinaryVectorNoAttributeHolder));
+
+ AssertSerializer, BinaryVectorInt8, sbyte>(classMap, nameof(BinaryVectorNoAttributeHolder.ValuesInt8), BinaryVectorDataType.Int8);
+ AssertSerializer, BinaryVectorPackedBit, byte>(classMap, nameof(BinaryVectorNoAttributeHolder.ValuesPackedBit), BinaryVectorDataType.PackedBit);
+ AssertSerializer, BinaryVectorFloat32, float>(classMap, nameof(BinaryVectorNoAttributeHolder.ValuesFloat), BinaryVectorDataType.Float32);
+ }
+
+ private void AssertSerializer(BsonClassMap classMap, string memberName, BinaryVectorDataType binaryVectorDataType)
+ where TSerializer : BinaryVectorSerializerBase
+ where TITem : struct
+ {
+ var memberMap = classMap.GetMemberMap(memberName);
+ var serializer = memberMap.GetSerializer();
+
+ var vectorSerializer = serializer.Should().BeOfType().Subject;
+
+ vectorSerializer.VectorDataType.Should().Be(binaryVectorDataType);
+ }
+
+ public class ArrayHolder
+ {
+ [BinaryVector(BinaryVectorDataType.Int8)]
+ public byte[] ValuesByte { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.Int8)]
+ public sbyte[] ValuesSByte { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.PackedBit)]
+ public byte[] ValuesPackedBit { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.Float32)]
+ public float[] ValuesFloat { get; set; }
+ }
+
+ public class BinaryVectorHolder
+ {
+ [BinaryVector(BinaryVectorDataType.Int8)]
+ public BinaryVectorInt8 ValuesInt8 { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.PackedBit)]
+ public BinaryVectorPackedBit ValuesPackedBit { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.Float32)]
+ public BinaryVectorFloat32 ValuesFloat { get; set; }
+ }
+
+ public class BinaryVectorNoAttributeHolder
+ {
+ public BinaryVectorInt8 ValuesInt8 { get; set; }
+
+ public BinaryVectorPackedBit ValuesPackedBit { get; set; }
+
+ public BinaryVectorFloat32 ValuesFloat { get; set; }
+ }
+
+ public class MemoryHolder
+ {
+ [BinaryVector(BinaryVectorDataType.Int8)]
+ public Memory ValuesByte { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.Int8)]
+ public Memory ValuesSByte { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.PackedBit)]
+ public Memory ValuesPackedBit { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.Float32)]
+ public Memory ValuesFloat { get; set; }
+ }
+
+ public class ReadOnlyMemoryHolder
+ {
+ [BinaryVector(BinaryVectorDataType.Int8)]
+ public ReadOnlyMemory ValuesByte { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.Int8)]
+ public ReadOnlyMemory ValuesSByte { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.PackedBit)]
+ public ReadOnlyMemory ValuesPackedBit { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.Float32)]
+ public ReadOnlyMemory ValuesFloat { get; set; }
+ }
+
+ public class InvalidTypesHolder
+ {
+ [BinaryVector(BinaryVectorDataType.Int8)]
+ public List List { get; set; }
+
+ [BinaryVector(BinaryVectorDataType.Int8)]
+ public Dictionary Dictionary { get; set; }
+ }
+ }
+}
diff --git a/tests/MongoDB.Bson.Tests/Serialization/BinaryVectorReaderTests.cs b/tests/MongoDB.Bson.Tests/Serialization/BinaryVectorReaderTests.cs
new file mode 100644
index 00000000000..a378e793de3
--- /dev/null
+++ b/tests/MongoDB.Bson.Tests/Serialization/BinaryVectorReaderTests.cs
@@ -0,0 +1,35 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using FluentAssertions;
+using MongoDB.Bson.Serialization;
+using Xunit;
+
+namespace MongoDB.Bson.Tests.Serialization
+{
+ public class BinaryVectorReaderTests
+ {
+ [Fact]
+ public void ReadBinaryVector_should_throw_on_type_mismatch_for_Int8()
+ {
+ byte[] vectorBsonData = [(byte)BinaryVectorDataType.Int8, 0, 1];
+
+ var exception = Record.Exception(() => BinaryVectorReader.ReadBinaryVector(vectorBsonData));
+ exception.Should().NotBeNull();
+ exception.Message.Should().Contain($"Expected {typeof(sbyte)}");
+ exception.Message.Should().Contain($"but found {typeof(byte)}");
+ }
+ }
+}
diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/BinaryVectorSerializerTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/BinaryVectorSerializerTests.cs
new file mode 100644
index 00000000000..4394c626c76
--- /dev/null
+++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/BinaryVectorSerializerTests.cs
@@ -0,0 +1,421 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using FluentAssertions;
+using MongoDB.Bson.IO;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Serializers;
+using Xunit;
+
+namespace MongoDB.Bson.Tests.Serialization
+{
+ public class BinaryVectorSerializerTests
+ {
+ [Theory]
+ [MemberData(nameof(TestData))]
+ public void ArrayAsBinaryVectorSerializer_should_deserialize_bson_vector(BinaryVectorDataType dataType, int elementCount, T _)
+ where T : struct
+ {
+ var subject = new ArrayAsBinaryVectorSerializer(dataType);
+
+ var (expectedArray, vectorBson) = GetTestData(dataType, elementCount, 0);
+
+ var actualArray = DeserializeFromBinaryData(vectorBson, subject);
+
+ actualArray.ShouldAllBeEquivalentTo(expectedArray);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestData))]
+ public void ArrayAsBinaryVectorSerializer_should_serialize_bson_vector(BinaryVectorDataType dataType, int elementCount, T _)
+ where T : struct
+ {
+ var subject = new ArrayAsBinaryVectorSerializer(dataType);
+
+ var (array, expectedBson) = GetTestData(dataType, elementCount, 0);
+
+ var binaryData = SerializeToBinaryData(array, subject);
+
+ Assert.Equal(BsonBinarySubType.Vector, binaryData.SubType);
+ Assert.Equal(expectedBson, binaryData.Bytes);
+ }
+
+ [Fact]
+ public void ArrayAsBinaryVectorSerializer_should_throw_on_non_zero_padding()
+ {
+ var subject = new ArrayAsBinaryVectorSerializer(BinaryVectorDataType.PackedBit);
+
+ var (expectedArray, vectorBson) = GetTestData(BinaryVectorDataType.PackedBit, 2, 1);
+
+ var exception = Record.Exception(() => DeserializeFromBinaryData(vectorBson, subject));
+ exception.Should().BeOfType();
+ exception.Message.Should().Contain("padding");
+ }
+
+ [Theory]
+ [MemberData(nameof(TestDataBinaryVector))]
+ public void BinaryVectorSerializer_should_serialize_bson_vector(BinaryVectorDataType dataType, int elementsCount, int bitsPadding, T _)
+ where T : struct
+ {
+ var subject = CreateBinaryVectorSerializer(dataType);
+
+ var (vector, expectedBson) = GetTestDataBinaryVector(dataType, elementsCount, (byte)bitsPadding);
+
+ var binaryData = SerializeToBinaryData(vector, subject);
+
+ Assert.Equal(BsonBinarySubType.Vector, binaryData.SubType);
+ Assert.Equal(expectedBson, binaryData.Bytes);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestDataBinaryVector))]
+ public void BinaryVectorSerializer_should_deserialize_bson_vector(BinaryVectorDataType dataType, int elementsCount, int bitsPadding, T _)
+ where T : struct
+ {
+ var subject = CreateBinaryVectorSerializer(dataType);
+
+ var (vector, vectorBson) = GetTestDataBinaryVector(dataType, elementsCount, (byte)bitsPadding);
+ var expectedArray = vector.Data.ToArray();
+ var expectedType = (dataType) switch
+ {
+ BinaryVectorDataType.Float32 => typeof(BinaryVectorFloat32),
+ BinaryVectorDataType.PackedBit => typeof(BinaryVectorPackedBit),
+ BinaryVectorDataType.Int8 => typeof(BinaryVectorInt8),
+ _ => throw new ArgumentOutOfRangeException(nameof(dataType))
+ };
+
+ var binaryVector = DeserializeFromBinaryData>(vectorBson, subject);
+
+ binaryVector.Should().BeOfType(expectedType);
+ binaryVector.Data.ToArray().ShouldBeEquivalentTo(expectedArray);
+
+ if (binaryVector is BinaryVectorPackedBit vectorPackedBit)
+ {
+ vectorPackedBit.Padding.Should().Be((byte)bitsPadding);
+ }
+ }
+
+ [Theory]
+ [InlineData(BinaryVectorDataType.Int8, typeof(BinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.Int8, typeof(ArrayAsBinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.Int8, typeof(ReadOnlyMemoryAsBinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.Int8, typeof(MemoryAsBinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.PackedBit, typeof(BinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.PackedBit, typeof(ArrayAsBinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.PackedBit, typeof(ReadOnlyMemoryAsBinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.PackedBit, typeof(MemoryAsBinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.PackedBit, typeof(MemoryAsBinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.Float32, typeof(BinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.Float32, typeof(ArrayAsBinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.Float32, typeof(ReadOnlyMemoryAsBinaryVectorSerializer))]
+ [InlineData(BinaryVectorDataType.Float32, typeof(MemoryAsBinaryVectorSerializer))]
+ public void BinaryVectorSerializer_should_throw_on_datatype_and_itemtype_mismatch(BinaryVectorDataType dataType, Type serializerType)
+ {
+ var itemType = serializerType.BaseType.GetGenericArguments().ElementAt(1);
+
+ var exception = Record.Exception(() => Activator.CreateInstance(serializerType, dataType)).InnerException;
+ exception.Should().BeOfType();
+
+ exception.Message.Should().Contain(itemType.ToString());
+ exception.Message.Should().Contain(dataType.ToString());
+ }
+
+ [Fact]
+ public void BinaryVectorSerializer_should_roundtrip_binaryvector_without_attribute()
+ {
+ var binaryVectorHolder = new BinaryVectorNoAttributeHolder()
+ {
+ ValuesFloat = new BinaryVectorFloat32(new float[] { 1.1f, 2.2f, 3.3f }),
+ ValuesInt8 = new BinaryVectorInt8(new sbyte[] { -1, 2, 3 }),
+ ValuesPackedBit = new BinaryVectorPackedBit(new byte[] { 1, 2, 3 }, 0)
+ };
+
+ var bson = binaryVectorHolder.ToBson();
+
+ var binaryVectorHolderDehydrated = BsonSerializer.Deserialize(bson);
+
+ binaryVectorHolderDehydrated.ValuesFloat.Data.ToArray().ShouldBeEquivalentTo(binaryVectorHolder.ValuesFloat.Data.ToArray());
+ binaryVectorHolderDehydrated.ValuesInt8.Data.ToArray().ShouldBeEquivalentTo(binaryVectorHolder.ValuesInt8.Data.ToArray());
+ binaryVectorHolderDehydrated.ValuesPackedBit.Data.ToArray().ShouldBeEquivalentTo(binaryVectorHolder.ValuesPackedBit.Data.ToArray());
+ }
+
+ [Theory]
+ [MemberData(nameof(TestData))]
+ public void MemoryAsBinaryVectorSerializer_should_serialize_bson_vector(BinaryVectorDataType dataType, int elementCount, T _)
+ where T : struct
+ {
+ var subject = new MemoryAsBinaryVectorSerializer(dataType);
+
+ var (elements, expectedBson) = GetTestData(dataType, elementCount, 0);
+ var memory = new Memory(elements);
+
+ var binaryData = SerializeToBinaryData(memory, subject);
+
+ Assert.Equal(BsonBinarySubType.Vector, binaryData.SubType);
+ Assert.Equal(expectedBson, binaryData.Bytes);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestData))]
+ public void MemoryAsBinaryVectorSerializer_should_deserialize_bson_vector(BinaryVectorDataType dataType, int elementCount, T _)
+ where T : struct
+ {
+ var subject = new MemoryAsBinaryVectorSerializer(dataType);
+
+ var (expectedArray, vectorBson) = GetTestData(dataType, elementCount, 0);
+
+ var actualMemory = DeserializeFromBinaryData>(vectorBson, subject);
+
+ actualMemory.ToArray().ShouldBeEquivalentTo(expectedArray);
+ }
+
+ [Fact]
+ public void MemoryAsBinaryVectorSerializer_should_throw_on_non_zero_padding()
+ {
+ var subject = new ReadOnlyMemoryAsBinaryVectorSerializer(BinaryVectorDataType.PackedBit);
+
+ var (expectedArray, vectorBson) = GetTestData(BinaryVectorDataType.PackedBit, 2, 1);
+
+ var exception = Record.Exception(() => DeserializeFromBinaryData>(vectorBson, subject));
+ exception.Should().BeOfType();
+ exception.Message.Should().Contain("padding");
+ }
+
+ [Theory]
+ [MemberData(nameof(TestData))]
+ public void ReadOnlyMemoryAsBinaryVectorSerializer_should_serialize_bson_vector(BinaryVectorDataType dataType, int elementCount, T _)
+ where T : struct
+ {
+ var subject = new ReadOnlyMemoryAsBinaryVectorSerializer(dataType);
+
+ var (elements, expectedBson) = GetTestData(dataType, elementCount, 0);
+ var memory = new ReadOnlyMemory(elements);
+
+ var binaryData = SerializeToBinaryData(memory, subject);
+
+ Assert.Equal(BsonBinarySubType.Vector, binaryData.SubType);
+ Assert.Equal(expectedBson, binaryData.Bytes);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestData))]
+ public void ReadOnlyMemoryAsBinaryVectorSerializer_should_deserialize_bson_vector(BinaryVectorDataType dataType, int elementCount, T _)
+ where T : struct
+ {
+ var subject = new ReadOnlyMemoryAsBinaryVectorSerializer(dataType);
+
+ var (expectedArray, vectorBson) = GetTestData(dataType, elementCount, 0);
+
+ var readonlyMemory = DeserializeFromBinaryData>(vectorBson, subject);
+
+ readonlyMemory.ToArray().ShouldBeEquivalentTo(expectedArray);
+ }
+
+ [Fact]
+ public void ReadonlyMemoryAsBinaryVectorSerializer_should_throw_on_non_zero_padding()
+ {
+ var subject = new ReadOnlyMemoryAsBinaryVectorSerializer(BinaryVectorDataType.PackedBit);
+
+ var (expectedArray, vectorBson) = GetTestData(BinaryVectorDataType.PackedBit, 2, 1);
+
+ var exception = Record.Exception(() => DeserializeFromBinaryData>(vectorBson, subject));
+ exception.Should().BeOfType();
+ exception.Message.Should().Contain("padding");
+ }
+
+ [Fact]
+ public void Equals_null_should_return_false()
+ {
+ var x = new ArrayAsBinaryVectorSerializer(BinaryVectorDataType.Float32);
+
+ var result = x.Equals(null);
+
+ result.Should().Be(false);
+ }
+
+ [Fact]
+ public void Equals_object_should_return_false()
+ {
+ var x = new ArrayAsBinaryVectorSerializer(BinaryVectorDataType.Float32);
+ var y = new object();
+
+ var result = x.Equals(y);
+
+ result.Should().Be(false);
+ }
+
+ [Fact]
+ public void Equals_self_should_return_true()
+ {
+ var x = new ArrayAsBinaryVectorSerializer(BinaryVectorDataType.Float32);
+
+ var result = x.Equals(x);
+
+ result.Should().Be(true);
+ }
+
+ [Fact]
+ public void Equals_with_equal_fields_should_return_true()
+ {
+ var x = new ArrayAsBinaryVectorSerializer(BinaryVectorDataType.Float32);
+ var y = new ArrayAsBinaryVectorSerializer(BinaryVectorDataType.Float32);
+
+ var result = x.Equals(y);
+
+ result.Should().Be(true);
+ }
+
+ [Fact]
+ public void Equals_with_not_equal_field_should_return_false()
+ {
+ var x = new ArrayAsBinaryVectorSerializer(BinaryVectorDataType.Float32);
+ var y = new ArrayAsBinaryVectorSerializer(BinaryVectorDataType.PackedBit);
+
+ var result = x.Equals(y);
+
+ result.Should().Be(false);
+ }
+
+ [Fact]
+ public void GetHashCode_should_return_zero()
+ {
+ var x = new ArrayAsBinaryVectorSerializer(BinaryVectorDataType.Float32);
+
+ var result = x.GetHashCode();
+
+ result.Should().Be(0);
+ }
+
+ private TCollection DeserializeFromBinaryData(byte[] vectorBson, IBsonSerializer serializer)
+ {
+ var binaryData = new BsonBinaryData(vectorBson, BsonBinarySubType.Vector);
+ var bsonDocument = new BsonDocument("vector", binaryData);
+ var bson = bsonDocument.ToBson();
+
+ using var memoryStream = new MemoryStream(bson);
+ using var reader = new BsonBinaryReader(memoryStream, new());
+ var context = BsonDeserializationContext.CreateRoot(reader);
+
+ reader.ReadStartDocument();
+ reader.ReadName("vector");
+
+ var result = serializer.Deserialize(context, new());
+
+ reader.ReadEndDocument();
+
+ return (TCollection)result;
+ }
+
+ private BsonBinaryData SerializeToBinaryData(TCollection collection, IBsonSerializer serializer)
+ {
+ using var memoryStream = new MemoryStream();
+ using var writer = new BsonBinaryWriter(memoryStream, new());
+ var context = BsonSerializationContext.CreateRoot(writer);
+
+ writer.WriteStartDocument();
+ writer.WriteName("vector");
+
+ serializer.Serialize(context, new(), collection);
+
+ writer.WriteEndDocument();
+
+ var document = BsonSerializer.Deserialize(memoryStream.ToArray());
+ var binaryData = document["vector"].AsBsonBinaryData;
+ return binaryData;
+ }
+
+ public readonly static IEnumerable