From 2b0e9d4327b9965b380666be3eb9a093fe0adc6e Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 7 Jan 2025 13:42:30 -0800 Subject: [PATCH 1/4] Rename Vector class names. JAVA-5710 --- bson/src/main/org/bson/BsonBinary.java | 10 +- bson/src/main/org/bson/BsonBinarySubType.java | 2 +- bson/src/main/org/bson/Float32Vector.java | 79 ---- bson/src/main/org/bson/Int8Vector.java | 80 ---- bson/src/main/org/bson/PackedBitVector.java | 101 ----- bson/src/main/org/bson/Vector.java | 201 ---------- .../org/bson/codecs/ContainerCodecHelper.java | 4 +- .../org/bson/codecs/Float32VectorCodec.java | 14 +- .../main/org/bson/codecs/Int8VectorCodec.java | 14 +- .../org/bson/codecs/PackedBitVectorCodec.java | 14 +- .../src/main/org/bson/codecs/VectorCodec.java | 14 +- .../bson/internal/vector/VectorHelper.java | 42 +-- .../test/unit/org/bson/BsonBinaryTest.java | 40 +- .../unit/org/bson/BsonBinaryWriterTest.java | 2 +- bson/src/test/unit/org/bson/VectorTest.java | 179 --------- .../org/bson/codecs/DocumentCodecTest.java | 8 +- .../ValueCodecProviderSpecification.groovy | 16 +- .../unit/org/bson/codecs/VectorCodecTest.java | 152 -------- .../bson/vector/VectorGenericBsonTest.java | 276 -------------- .../com/mongodb/client/model/Aggregates.java | 8 +- ...AggregatesVectorSearchIntegrationTest.java | 353 ------------------ .../model/AggregatesSpecification.groovy | 12 +- .../model/search/VectorSearchOptionsTest.java | 126 ------- .../client/vector/VectorFunctionalTest.java | 30 -- .../vector/AbstractVectorFunctionalTest.java | 346 ----------------- .../client/vector/VectorFunctionalTest.java | 28 -- 26 files changed, 100 insertions(+), 2051 deletions(-) delete mode 100644 bson/src/main/org/bson/Float32Vector.java delete mode 100644 bson/src/main/org/bson/Int8Vector.java delete mode 100644 bson/src/main/org/bson/PackedBitVector.java delete mode 100644 bson/src/main/org/bson/Vector.java delete mode 100644 bson/src/test/unit/org/bson/VectorTest.java delete mode 100644 bson/src/test/unit/org/bson/codecs/VectorCodecTest.java delete mode 100644 bson/src/test/unit/org/bson/vector/VectorGenericBsonTest.java delete mode 100644 driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesVectorSearchIntegrationTest.java delete mode 100644 driver-core/src/test/unit/com/mongodb/client/model/search/VectorSearchOptionsTest.java delete mode 100644 driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/VectorFunctionalTest.java delete mode 100644 driver-sync/src/test/functional/com/mongodb/client/vector/AbstractVectorFunctionalTest.java delete mode 100644 driver-sync/src/test/functional/com/mongodb/client/vector/VectorFunctionalTest.java diff --git a/bson/src/main/org/bson/BsonBinary.java b/bson/src/main/org/bson/BsonBinary.java index 8590c2920be..554072116aa 100644 --- a/bson/src/main/org/bson/BsonBinary.java +++ b/bson/src/main/org/bson/BsonBinary.java @@ -93,12 +93,12 @@ public BsonBinary(final UUID uuid) { } /** - * Constructs a {@linkplain BsonBinarySubType#VECTOR subtype 9} {@link BsonBinary} from the given {@link Vector}. + * Constructs a {@linkplain BsonBinarySubType#VECTOR subtype 9} {@link BsonBinary} from the given {@link BinaryVector}. * - * @param vector the {@link Vector} + * @param vector the {@link BinaryVector} * @since 5.3 */ - public BsonBinary(final Vector vector) { + public BsonBinary(final BinaryVector vector) { if (vector == null) { throw new IllegalArgumentException("Vector must not be null"); } @@ -145,13 +145,13 @@ public UUID asUuid() { } /** - * Returns the binary as a {@link Vector}. The {@linkplain #getType() subtype} must be {@linkplain BsonBinarySubType#VECTOR 9}. + * Returns the binary as a {@link BinaryVector}. The {@linkplain #getType() subtype} must be {@linkplain BsonBinarySubType#VECTOR 9}. * * @return the vector * @throws BsonInvalidOperationException if the binary subtype is not {@link BsonBinarySubType#VECTOR}. * @since 5.3 */ - public Vector asVector() { + public BinaryVector asVector() { if (type != BsonBinarySubType.VECTOR.getValue()) { throw new BsonInvalidOperationException("type must be a Vector subtype."); } diff --git a/bson/src/main/org/bson/BsonBinarySubType.java b/bson/src/main/org/bson/BsonBinarySubType.java index 7b5948b4efc..08c29e2ef09 100644 --- a/bson/src/main/org/bson/BsonBinarySubType.java +++ b/bson/src/main/org/bson/BsonBinarySubType.java @@ -78,7 +78,7 @@ public enum BsonBinarySubType { * * @mongodb.server.release 6.0 * @since 5.3 - * @see Vector + * @see BinaryVector */ VECTOR((byte) 0x09), diff --git a/bson/src/main/org/bson/Float32Vector.java b/bson/src/main/org/bson/Float32Vector.java deleted file mode 100644 index 9678003b72f..00000000000 --- a/bson/src/main/org/bson/Float32Vector.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package org.bson; - -import java.util.Arrays; - -import static org.bson.assertions.Assertions.assertNotNull; - -/** - * Represents a vector of 32-bit floating-point numbers, where each element in the vector is a float. - *

- * The {@link Float32Vector} is used to store and retrieve data efficiently using the BSON Binary Subtype 9 format. - * - * @mongodb.server.release 6.0 - * @see Vector#floatVector(float[]) - * @see BsonBinary#BsonBinary(Vector) - * @see BsonBinary#asVector() - * @since 5.3 - */ -public final class Float32Vector extends Vector { - - private final float[] data; - - Float32Vector(final float[] vectorData) { - super(DataType.FLOAT32); - this.data = assertNotNull(vectorData); - } - - /** - * Retrieve the underlying float array representing this {@link Float32Vector}, where each float - * represents an element of a vector. - *

- * NOTE: The underlying float array is not copied; changes to the returned array will be reflected in this instance. - * - * @return the underlying float array representing this {@link Float32Vector} vector. - */ - public float[] getData() { - return assertNotNull(data); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Float32Vector that = (Float32Vector) o; - return Arrays.equals(data, that.data); - } - - @Override - public int hashCode() { - return Arrays.hashCode(data); - } - - @Override - public String toString() { - return "Float32Vector{" - + "data=" + Arrays.toString(data) - + ", dataType=" + getDataType() - + '}'; - } -} diff --git a/bson/src/main/org/bson/Int8Vector.java b/bson/src/main/org/bson/Int8Vector.java deleted file mode 100644 index b61e6bfee55..00000000000 --- a/bson/src/main/org/bson/Int8Vector.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package org.bson; - -import java.util.Arrays; -import java.util.Objects; - -import static org.bson.assertions.Assertions.assertNotNull; - -/** - * Represents a vector of 8-bit signed integers, where each element in the vector is a byte. - *

- * The {@link Int8Vector} is used to store and retrieve data efficiently using the BSON Binary Subtype 9 format. - * - * @mongodb.server.release 6.0 - * @see Vector#int8Vector(byte[]) - * @see BsonBinary#BsonBinary(Vector) - * @see BsonBinary#asVector() - * @since 5.3 - */ -public final class Int8Vector extends Vector { - - private byte[] data; - - Int8Vector(final byte[] data) { - super(DataType.INT8); - this.data = assertNotNull(data); - } - - /** - * Retrieve the underlying byte array representing this {@link Int8Vector} vector, where each byte represents - * an element of a vector. - *

- * NOTE: The underlying byte array is not copied; changes to the returned array will be reflected in this instance. - * - * @return the underlying byte array representing this {@link Int8Vector} vector. - */ - public byte[] getData() { - return assertNotNull(data); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Int8Vector that = (Int8Vector) o; - return Objects.deepEquals(data, that.data); - } - - @Override - public int hashCode() { - return Arrays.hashCode(data); - } - - @Override - public String toString() { - return "Int8Vector{" - + "data=" + Arrays.toString(data) - + ", dataType=" + getDataType() - + '}'; - } -} diff --git a/bson/src/main/org/bson/PackedBitVector.java b/bson/src/main/org/bson/PackedBitVector.java deleted file mode 100644 index a5dd8f4dcdf..00000000000 --- a/bson/src/main/org/bson/PackedBitVector.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package org.bson; - -import java.util.Arrays; -import java.util.Objects; - -import static org.bson.assertions.Assertions.assertNotNull; - -/** - * Represents a packed bit vector, where each element of the vector is represented by a single bit (0 or 1). - *

- * The {@link PackedBitVector} is used to store data efficiently using the BSON Binary Subtype 9 format. - * - * @mongodb.server.release 6.0 - * @see Vector#packedBitVector(byte[], byte) - * @see BsonBinary#BsonBinary(Vector) - * @see BsonBinary#asVector() - * @since 5.3 - */ -public final class PackedBitVector extends Vector { - - private final byte padding; - private final byte[] data; - - PackedBitVector(final byte[] data, final byte padding) { - super(DataType.PACKED_BIT); - this.data = assertNotNull(data); - this.padding = padding; - } - - /** - * Retrieve the underlying byte array representing this {@link PackedBitVector} vector, where - * each bit represents an element of the vector (either 0 or 1). - *

- * Note that the {@linkplain #getPadding() padding value} should be considered when interpreting the final byte of the array, - * as it indicates how many least-significant bits are to be ignored. - * - * @return the underlying byte array representing this {@link PackedBitVector} vector. - * @see #getPadding() - */ - public byte[] getData() { - return assertNotNull(data); - } - - /** - * Returns the padding value for this vector. - * - *

Padding refers to the number of least-significant bits in the final byte that are ignored when retrieving - * {@linkplain #getData() the vector array}. For instance, if the padding value is 3, this means that the last byte contains - * 3 least-significant unused bits, which should be disregarded during operations.

- *

- * - * NOTE: The underlying byte array is not copied; changes to the returned array will be reflected in this instance. - * - * @return the padding value (between 0 and 7). - */ - public byte getPadding() { - return this.padding; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PackedBitVector that = (PackedBitVector) o; - return padding == that.padding && Arrays.equals(data, that.data); - } - - @Override - public int hashCode() { - return Objects.hash(padding, Arrays.hashCode(data)); - } - - @Override - public String toString() { - return "PackedBitVector{" - + "padding=" + padding - + ", data=" + Arrays.toString(data) - + ", dataType=" + getDataType() - + '}'; - } -} diff --git a/bson/src/main/org/bson/Vector.java b/bson/src/main/org/bson/Vector.java deleted file mode 100644 index d267387d727..00000000000 --- a/bson/src/main/org/bson/Vector.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package org.bson; - -import static org.bson.assertions.Assertions.isTrueArgument; -import static org.bson.assertions.Assertions.notNull; - -/** - * Represents a vector that is stored and retrieved using the BSON Binary Subtype 9 format. - * This class supports multiple vector {@link DataType}'s and provides static methods to create - * vectors. - *

- * Vectors are densely packed arrays of numbers, all the same type, which are stored efficiently - * in BSON using a binary format. - *

- * NOTE: This class should be treated as sealed: it must not be extended or implemented by consumers of the library. - * - * @mongodb.server.release 6.0 - * @see BsonBinary - * @since 5.3 - */ -public abstract class Vector { - private final DataType dataType; - - Vector(final DataType dataType) { - this.dataType = dataType; - } - - /** - * Creates a vector with the {@link DataType#PACKED_BIT} data type. - *

- * A {@link DataType#PACKED_BIT} vector is a binary quantized vector where each element of a vector is represented by a single bit (0 or 1). Each byte - * can hold up to 8 bits (vector elements). The padding parameter is used to specify how many least-significant bits in the final byte - * should be ignored.

- * - *

For example, a vector with two bytes and a padding of 4 would have the following structure:

- *
-     * Byte 1: 238 (binary: 11101110)
-     * Byte 2: 224 (binary: 11100000)
-     * Padding: 4 (ignore the last 4 bits in Byte 2)
-     * Resulting vector: 12 bits: 111011101110
-     * 
- *

- * NOTE: The byte array `data` is not copied; changes to the provided array will be reflected - * in the created {@link PackedBitVector} instance. - * - * @param data The byte array representing the packed bit vector data. Each byte can store 8 bits. - * @param padding The number of least-significant bits (0 to 7) to ignore in the final byte of the vector data. - * @return A {@link PackedBitVector} instance with the {@link DataType#PACKED_BIT} data type. - * @throws IllegalArgumentException If the padding value is greater than 7. - */ - public static PackedBitVector packedBitVector(final byte[] data, final byte padding) { - notNull("data", data); - isTrueArgument("Padding must be between 0 and 7 bits. Provided padding: " + padding, padding >= 0 && padding <= 7); - isTrueArgument("Padding must be 0 if vector is empty. Provided padding: " + padding, padding == 0 || data.length > 0); - return new PackedBitVector(data, padding); - } - - /** - * Creates a vector with the {@link DataType#INT8} data type. - * - *

A {@link DataType#INT8} vector is a vector of 8-bit signed integers where each byte in the vector represents an element of a vector, - * with values in the range [-128, 127].

- *

- * NOTE: The byte array `data` is not copied; changes to the provided array will be reflected - * in the created {@link Int8Vector} instance. - * - * @param data The byte array representing the {@link DataType#INT8} vector data. - * @return A {@link Int8Vector} instance with the {@link DataType#INT8} data type. - */ - public static Int8Vector int8Vector(final byte[] data) { - notNull("data", data); - return new Int8Vector(data); - } - - /** - * Creates a vector with the {@link DataType#FLOAT32} data type. - *

- * A {@link DataType#FLOAT32} vector is a vector of floating-point numbers, where each element in the vector is a float.

- *

- * NOTE: The float array `data` is not copied; changes to the provided array will be reflected - * in the created {@link Float32Vector} instance. - * - * @param data The float array representing the {@link DataType#FLOAT32} vector data. - * @return A {@link Float32Vector} instance with the {@link DataType#FLOAT32} data type. - */ - public static Float32Vector floatVector(final float[] data) { - notNull("data", data); - return new Float32Vector(data); - } - - /** - * Returns the {@link PackedBitVector}. - * - * @return {@link PackedBitVector}. - * @throws IllegalStateException if this vector is not of type {@link DataType#PACKED_BIT}. Use {@link #getDataType()} to check the vector - * type before calling this method. - */ - public PackedBitVector asPackedBitVector() { - ensureType(DataType.PACKED_BIT); - return (PackedBitVector) this; - } - - /** - * Returns the {@link Int8Vector}. - * - * @return {@link Int8Vector}. - * @throws IllegalStateException if this vector is not of type {@link DataType#INT8}. Use {@link #getDataType()} to check the vector - * type before calling this method. - */ - public Int8Vector asInt8Vector() { - ensureType(DataType.INT8); - return (Int8Vector) this; - } - - /** - * Returns the {@link Float32Vector}. - * - * @return {@link Float32Vector}. - * @throws IllegalStateException if this vector is not of type {@link DataType#FLOAT32}. Use {@link #getDataType()} to check the vector - * type before calling this method. - */ - public Float32Vector asFloat32Vector() { - ensureType(DataType.FLOAT32); - return (Float32Vector) this; - } - - /** - * Returns {@link DataType} of the vector. - * - * @return the data type of the vector. - */ - public DataType getDataType() { - return this.dataType; - } - - - private void ensureType(final DataType expected) { - if (this.dataType != expected) { - throw new IllegalStateException("Expected vector data type " + expected + ", but found " + this.dataType); - } - } - - /** - * Represents the data type (dtype) of a vector. - *

- * Each dtype determines how the data in the vector is stored, including how many bits are used to represent each element - * in the vector. - * - * @mongodb.server.release 6.0 - * @since 5.3 - */ - public enum DataType { - /** - * An INT8 vector is a vector of 8-bit signed integers. The vector is stored as an array of bytes, where each byte - * represents a signed integer in the range [-128, 127]. - */ - INT8((byte) 0x03), - /** - * A FLOAT32 vector is a vector of 32-bit floating-point numbers, where each element in the vector is a float. - */ - FLOAT32((byte) 0x27), - /** - * A PACKED_BIT vector is a binary quantized vector where each element of a vector is represented by a single bit (0 or 1). - * Each byte can hold up to 8 bits (vector elements). - */ - PACKED_BIT((byte) 0x10); - - private final byte value; - - DataType(final byte value) { - this.value = value; - } - - /** - * Returns the byte value associated with this {@link DataType}. - * - *

This value is used in the BSON binary format to indicate the data type of the vector.

- * - * @return the byte value representing the {@link DataType}. - */ - public byte getValue() { - return value; - } - } -} - diff --git a/bson/src/main/org/bson/codecs/ContainerCodecHelper.java b/bson/src/main/org/bson/codecs/ContainerCodecHelper.java index b454206d5e8..2243f209528 100644 --- a/bson/src/main/org/bson/codecs/ContainerCodecHelper.java +++ b/bson/src/main/org/bson/codecs/ContainerCodecHelper.java @@ -21,7 +21,7 @@ import org.bson.BsonType; import org.bson.Transformer; import org.bson.UuidRepresentation; -import org.bson.Vector; +import org.bson.BinaryVector; import org.bson.codecs.configuration.CodecConfigurationException; import org.bson.codecs.configuration.CodecRegistry; @@ -68,7 +68,7 @@ private static Codec getBinarySubTypeCodec(final BsonReader reader, final Codec binaryTypeCodec) { if (binarySubType == BsonBinarySubType.VECTOR.getValue()) { - Codec vectorCodec = registry.get(Vector.class, registry); + Codec vectorCodec = registry.get(BinaryVector.class, registry); if (vectorCodec != null) { return vectorCodec; } diff --git a/bson/src/main/org/bson/codecs/Float32VectorCodec.java b/bson/src/main/org/bson/codecs/Float32VectorCodec.java index a6df27e3f87..4983ff45dcf 100644 --- a/bson/src/main/org/bson/codecs/Float32VectorCodec.java +++ b/bson/src/main/org/bson/codecs/Float32VectorCodec.java @@ -21,21 +21,21 @@ import org.bson.BsonInvalidOperationException; import org.bson.BsonReader; import org.bson.BsonWriter; -import org.bson.Float32Vector; +import org.bson.Float32BinaryVector; /** - * Encodes and decodes {@link Float32Vector} objects. + * Encodes and decodes {@link Float32BinaryVector} objects. * */ -final class Float32VectorCodec implements Codec { +final class Float32VectorCodec implements Codec { @Override - public void encode(final BsonWriter writer, final Float32Vector vectorToEncode, final EncoderContext encoderContext) { + public void encode(final BsonWriter writer, final Float32BinaryVector vectorToEncode, final EncoderContext encoderContext) { writer.writeBinaryData(new BsonBinary(vectorToEncode)); } @Override - public Float32Vector decode(final BsonReader reader, final DecoderContext decoderContext) { + public Float32BinaryVector decode(final BsonReader reader, final DecoderContext decoderContext) { byte subType = reader.peekBinarySubType(); if (subType != BsonBinarySubType.VECTOR.getValue()) { @@ -49,8 +49,8 @@ public Float32Vector decode(final BsonReader reader, final DecoderContext decode } @Override - public Class getEncoderClass() { - return Float32Vector.class; + public Class getEncoderClass() { + return Float32BinaryVector.class; } } diff --git a/bson/src/main/org/bson/codecs/Int8VectorCodec.java b/bson/src/main/org/bson/codecs/Int8VectorCodec.java index a9a70f53746..963da625d7f 100644 --- a/bson/src/main/org/bson/codecs/Int8VectorCodec.java +++ b/bson/src/main/org/bson/codecs/Int8VectorCodec.java @@ -21,22 +21,22 @@ import org.bson.BsonInvalidOperationException; import org.bson.BsonReader; import org.bson.BsonWriter; -import org.bson.Int8Vector; +import org.bson.Int8BinaryVector; /** - * Encodes and decodes {@link Int8Vector} objects. + * Encodes and decodes {@link Int8BinaryVector} objects. * * @since 5.3 */ -final class Int8VectorCodec implements Codec { +final class Int8VectorCodec implements Codec { @Override - public void encode(final BsonWriter writer, final Int8Vector vectorToEncode, final EncoderContext encoderContext) { + public void encode(final BsonWriter writer, final Int8BinaryVector vectorToEncode, final EncoderContext encoderContext) { writer.writeBinaryData(new BsonBinary(vectorToEncode)); } @Override - public Int8Vector decode(final BsonReader reader, final DecoderContext decoderContext) { + public Int8BinaryVector decode(final BsonReader reader, final DecoderContext decoderContext) { byte subType = reader.peekBinarySubType(); if (subType != BsonBinarySubType.VECTOR.getValue()) { @@ -51,8 +51,8 @@ public Int8Vector decode(final BsonReader reader, final DecoderContext decoderCo @Override - public Class getEncoderClass() { - return Int8Vector.class; + public Class getEncoderClass() { + return Int8BinaryVector.class; } } diff --git a/bson/src/main/org/bson/codecs/PackedBitVectorCodec.java b/bson/src/main/org/bson/codecs/PackedBitVectorCodec.java index 6fcb9552955..28d76ead729 100644 --- a/bson/src/main/org/bson/codecs/PackedBitVectorCodec.java +++ b/bson/src/main/org/bson/codecs/PackedBitVectorCodec.java @@ -21,21 +21,21 @@ import org.bson.BsonInvalidOperationException; import org.bson.BsonReader; import org.bson.BsonWriter; -import org.bson.PackedBitVector; +import org.bson.PackedBitBinaryVector; /** - * Encodes and decodes {@link PackedBitVector} objects. + * Encodes and decodes {@link PackedBitBinaryVector} objects. * */ -final class PackedBitVectorCodec implements Codec { +final class PackedBitVectorCodec implements Codec { @Override - public void encode(final BsonWriter writer, final PackedBitVector vectorToEncode, final EncoderContext encoderContext) { + public void encode(final BsonWriter writer, final PackedBitBinaryVector vectorToEncode, final EncoderContext encoderContext) { writer.writeBinaryData(new BsonBinary(vectorToEncode)); } @Override - public PackedBitVector decode(final BsonReader reader, final DecoderContext decoderContext) { + public PackedBitBinaryVector decode(final BsonReader reader, final DecoderContext decoderContext) { byte subType = reader.peekBinarySubType(); if (subType != BsonBinarySubType.VECTOR.getValue()) { @@ -51,8 +51,8 @@ public PackedBitVector decode(final BsonReader reader, final DecoderContext deco @Override - public Class getEncoderClass() { - return PackedBitVector.class; + public Class getEncoderClass() { + return PackedBitBinaryVector.class; } } diff --git a/bson/src/main/org/bson/codecs/VectorCodec.java b/bson/src/main/org/bson/codecs/VectorCodec.java index 87d847664dc..1378ab094ee 100644 --- a/bson/src/main/org/bson/codecs/VectorCodec.java +++ b/bson/src/main/org/bson/codecs/VectorCodec.java @@ -21,21 +21,21 @@ import org.bson.BsonInvalidOperationException; import org.bson.BsonReader; import org.bson.BsonWriter; -import org.bson.Vector; +import org.bson.BinaryVector; /** - * Encodes and decodes {@link Vector} objects. + * Encodes and decodes {@link BinaryVector} objects. * */ - final class VectorCodec implements Codec { + final class VectorCodec implements Codec { @Override - public void encode(final BsonWriter writer, final Vector vectorToEncode, final EncoderContext encoderContext) { + public void encode(final BsonWriter writer, final BinaryVector vectorToEncode, final EncoderContext encoderContext) { writer.writeBinaryData(new BsonBinary(vectorToEncode)); } @Override - public Vector decode(final BsonReader reader, final DecoderContext decoderContext) { + public BinaryVector decode(final BsonReader reader, final DecoderContext decoderContext) { byte subType = reader.peekBinarySubType(); if (subType != BsonBinarySubType.VECTOR.getValue()) { @@ -48,8 +48,8 @@ public Vector decode(final BsonReader reader, final DecoderContext decoderContex } @Override - public Class getEncoderClass() { - return Vector.class; + public Class getEncoderClass() { + return BinaryVector.class; } } diff --git a/bson/src/main/org/bson/internal/vector/VectorHelper.java b/bson/src/main/org/bson/internal/vector/VectorHelper.java index 9dbf583d2b0..9f2657bf315 100644 --- a/bson/src/main/org/bson/internal/vector/VectorHelper.java +++ b/bson/src/main/org/bson/internal/vector/VectorHelper.java @@ -18,10 +18,10 @@ import org.bson.BsonBinary; import org.bson.BsonInvalidOperationException; -import org.bson.Float32Vector; -import org.bson.Int8Vector; -import org.bson.PackedBitVector; -import org.bson.Vector; +import org.bson.Float32BinaryVector; +import org.bson.Int8BinaryVector; +import org.bson.PackedBitBinaryVector; +import org.bson.BinaryVector; import org.bson.assertions.Assertions; import org.bson.types.Binary; @@ -35,9 +35,9 @@ *

* This class is not part of the public API and may be removed or changed at any time. * - * @see Vector + * @see BinaryVector * @see BsonBinary#asVector() - * @see BsonBinary#BsonBinary(Vector) + * @see BsonBinary#BsonBinary(BinaryVector) */ public final class VectorHelper { @@ -51,13 +51,13 @@ private VectorHelper() { private static final int METADATA_SIZE = 2; - public static byte[] encodeVectorToBinary(final Vector vector) { - Vector.DataType dataType = vector.getDataType(); + public static byte[] encodeVectorToBinary(final BinaryVector vector) { + BinaryVector.DataType dataType = vector.getDataType(); switch (dataType) { case INT8: return encodeVector(dataType.getValue(), ZERO_PADDING, vector.asInt8Vector().getData()); case PACKED_BIT: - PackedBitVector packedBitVector = vector.asPackedBitVector(); + PackedBitBinaryVector packedBitVector = vector.asPackedBitVector(); return encodeVector(dataType.getValue(), packedBitVector.getPadding(), packedBitVector.getData()); case FLOAT32: return encodeVector(dataType.getValue(), vector.asFloat32Vector().getData()); @@ -69,11 +69,11 @@ public static byte[] encodeVectorToBinary(final Vector vector) { /** * Decodes a vector from a binary representation. *

- * encodedVector is not mutated nor stored in the returned {@link Vector}. + * encodedVector is not mutated nor stored in the returned {@link BinaryVector}. */ - public static Vector decodeBinaryToVector(final byte[] encodedVector) { + public static BinaryVector decodeBinaryToVector(final byte[] encodedVector) { isTrue("Vector encoded array length must be at least 2, but found: " + encodedVector.length, encodedVector.length >= METADATA_SIZE); - Vector.DataType dataType = determineVectorDType(encodedVector[0]); + BinaryVector.DataType dataType = determineVectorDType(encodedVector[0]); byte padding = encodedVector[1]; switch (dataType) { case INT8: @@ -87,22 +87,22 @@ public static Vector decodeBinaryToVector(final byte[] encodedVector) { } } - private static Float32Vector decodeFloat32Vector(final byte[] encodedVector, final byte padding) { + private static Float32BinaryVector decodeFloat32Vector(final byte[] encodedVector, final byte padding) { isTrue("Padding must be 0 for FLOAT32 data type, but found: " + padding, padding == 0); - return Vector.floatVector(decodeLittleEndianFloats(encodedVector)); + return BinaryVector.floatVector(decodeLittleEndianFloats(encodedVector)); } - private static PackedBitVector decodePackedBitVector(final byte[] encodedVector, final byte padding) { + private static PackedBitBinaryVector decodePackedBitVector(final byte[] encodedVector, final byte padding) { byte[] packedBitVector = extractVectorData(encodedVector); isTrue("Padding must be 0 if vector is empty, but found: " + padding, padding == 0 || packedBitVector.length > 0); isTrue("Padding must be between 0 and 7 bits, but found: " + padding, padding >= 0 && padding <= 7); - return Vector.packedBitVector(packedBitVector, padding); + return BinaryVector.packedBitVector(packedBitVector, padding); } - private static Int8Vector decodeInt8Vector(final byte[] encodedVector, final byte padding) { + private static Int8BinaryVector decodeInt8Vector(final byte[] encodedVector, final byte padding) { isTrue("Padding must be 0 for INT8 data type, but found: " + padding, padding == 0); byte[] int8Vector = extractVectorData(encodedVector); - return Vector.int8Vector(int8Vector); + return BinaryVector.int8Vector(int8Vector); } private static byte[] extractVectorData(final byte[] encodedVector) { @@ -159,9 +159,9 @@ private static float[] decodeLittleEndianFloats(final byte[] encodedVector) { return floatArray; } - public static Vector.DataType determineVectorDType(final byte dType) { - Vector.DataType[] values = Vector.DataType.values(); - for (Vector.DataType value : values) { + public static BinaryVector.DataType determineVectorDType(final byte dType) { + BinaryVector.DataType[] values = BinaryVector.DataType.values(); + for (BinaryVector.DataType value : values) { if (value.getValue() == dType) { return value; } diff --git a/bson/src/test/unit/org/bson/BsonBinaryTest.java b/bson/src/test/unit/org/bson/BsonBinaryTest.java index 029c611c594..b47bcbf8a79 100644 --- a/bson/src/test/unit/org/bson/BsonBinaryTest.java +++ b/bson/src/test/unit/org/bson/BsonBinaryTest.java @@ -32,15 +32,15 @@ class BsonBinaryTest { - private static final byte FLOAT32_DTYPE = Vector.DataType.FLOAT32.getValue(); - private static final byte INT8_DTYPE = Vector.DataType.INT8.getValue(); - private static final byte PACKED_BIT_DTYPE = Vector.DataType.PACKED_BIT.getValue(); + private static final byte FLOAT32_DTYPE = BinaryVector.DataType.FLOAT32.getValue(); + private static final byte INT8_DTYPE = BinaryVector.DataType.INT8.getValue(); + private static final byte PACKED_BIT_DTYPE = BinaryVector.DataType.PACKED_BIT.getValue(); public static final int ZERO_PADDING = 0; @Test void shouldThrowExceptionWhenCreatingBsonBinaryWithNullVector() { // given - Vector vector = null; + BinaryVector vector = null; // when & then IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new BsonBinary(vector)); @@ -61,7 +61,7 @@ void shouldThrowExceptionWhenBsonBinarySubTypeIsNotVector(final BsonBinarySubTyp @ParameterizedTest(name = "{index}: {0}") @MethodSource("provideFloatVectors") - void shouldEncodeFloatVector(final Vector actualFloat32Vector, final byte[] expectedBsonEncodedVector) { + void shouldEncodeFloatVector(final BinaryVector actualFloat32Vector, final byte[] expectedBsonEncodedVector) { // when BsonBinary actualBsonBinary = new BsonBinary(actualFloat32Vector); byte[] actualBsonEncodedVector = actualBsonBinary.getData(); @@ -73,9 +73,9 @@ void shouldEncodeFloatVector(final Vector actualFloat32Vector, final byte[] expe @ParameterizedTest(name = "{index}: {0}") @MethodSource("provideFloatVectors") - void shouldDecodeFloatVector(final Float32Vector expectedFloatVector, final byte[] bsonEncodedVector) { + void shouldDecodeFloatVector(final Float32BinaryVector expectedFloatVector, final byte[] bsonEncodedVector) { // when - Float32Vector decodedVector = (Float32Vector) new BsonBinary(BsonBinarySubType.VECTOR, bsonEncodedVector).asVector(); + Float32BinaryVector decodedVector = (Float32BinaryVector) new BsonBinary(BsonBinarySubType.VECTOR, bsonEncodedVector).asVector(); // then assertEquals(expectedFloatVector, decodedVector); @@ -84,7 +84,7 @@ void shouldDecodeFloatVector(final Float32Vector expectedFloatVector, final byte private static Stream provideFloatVectors() { return Stream.of( arguments( - Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f, -1.0f, Float.MAX_VALUE, Float.MIN_VALUE, Float.POSITIVE_INFINITY, + BinaryVector.floatVector(new float[]{1.1f, 2.2f, 3.3f, -1.0f, Float.MAX_VALUE, Float.MIN_VALUE, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY}), new byte[]{FLOAT32_DTYPE, ZERO_PADDING, (byte) 205, (byte) 204, (byte) 140, (byte) 63, // 1.1f in little-endian @@ -98,13 +98,13 @@ private static Stream provideFloatVectors() { } ), arguments( - Vector.floatVector(new float[]{0.0f}), + BinaryVector.floatVector(new float[]{0.0f}), new byte[]{FLOAT32_DTYPE, ZERO_PADDING, (byte) 0, (byte) 0, (byte) 0, (byte) 0 // 0.0f in little-endian } ), arguments( - Vector.floatVector(new float[]{}), + BinaryVector.floatVector(new float[]{}), new byte[]{FLOAT32_DTYPE, ZERO_PADDING} ) ); @@ -112,7 +112,7 @@ private static Stream provideFloatVectors() { @ParameterizedTest(name = "{index}: {0}") @MethodSource("provideInt8Vectors") - void shouldEncodeInt8Vector(final Vector actualInt8Vector, final byte[] expectedBsonEncodedVector) { + void shouldEncodeInt8Vector(final BinaryVector actualInt8Vector, final byte[] expectedBsonEncodedVector) { // when BsonBinary actualBsonBinary = new BsonBinary(actualInt8Vector); byte[] actualBsonEncodedVector = actualBsonBinary.getData(); @@ -124,9 +124,9 @@ void shouldEncodeInt8Vector(final Vector actualInt8Vector, final byte[] expected @ParameterizedTest(name = "{index}: {0}") @MethodSource("provideInt8Vectors") - void shouldDecodeInt8Vector(final Int8Vector expectedInt8Vector, final byte[] bsonEncodedVector) { + void shouldDecodeInt8Vector(final Int8BinaryVector expectedInt8Vector, final byte[] bsonEncodedVector) { // when - Int8Vector decodedVector = (Int8Vector) new BsonBinary(BsonBinarySubType.VECTOR, bsonEncodedVector).asVector(); + Int8BinaryVector decodedVector = (Int8BinaryVector) new BsonBinary(BsonBinarySubType.VECTOR, bsonEncodedVector).asVector(); // then assertEquals(expectedInt8Vector, decodedVector); @@ -135,10 +135,10 @@ void shouldDecodeInt8Vector(final Int8Vector expectedInt8Vector, final byte[] bs private static Stream provideInt8Vectors() { return Stream.of( arguments( - Vector.int8Vector(new byte[]{Byte.MAX_VALUE, 1, 2, 3, 4, Byte.MIN_VALUE}), + BinaryVector.int8Vector(new byte[]{Byte.MAX_VALUE, 1, 2, 3, 4, Byte.MIN_VALUE}), new byte[]{INT8_DTYPE, ZERO_PADDING, Byte.MAX_VALUE, 1, 2, 3, 4, Byte.MIN_VALUE }), - arguments(Vector.int8Vector(new byte[]{}), + arguments(BinaryVector.int8Vector(new byte[]{}), new byte[]{INT8_DTYPE, ZERO_PADDING} ) ); @@ -146,7 +146,7 @@ private static Stream provideInt8Vectors() { @ParameterizedTest @MethodSource("providePackedBitVectors") - void shouldEncodePackedBitVector(final Vector actualPackedBitVector, final byte[] expectedBsonEncodedVector) { + void shouldEncodePackedBitVector(final BinaryVector actualPackedBitVector, final byte[] expectedBsonEncodedVector) { // when BsonBinary actualBsonBinary = new BsonBinary(actualPackedBitVector); byte[] actualBsonEncodedVector = actualBsonBinary.getData(); @@ -158,9 +158,9 @@ void shouldEncodePackedBitVector(final Vector actualPackedBitVector, final byte[ @ParameterizedTest @MethodSource("providePackedBitVectors") - void shouldDecodePackedBitVector(final PackedBitVector expectedPackedBitVector, final byte[] bsonEncodedVector) { + void shouldDecodePackedBitVector(final PackedBitBinaryVector expectedPackedBitVector, final byte[] bsonEncodedVector) { // when - PackedBitVector decodedVector = (PackedBitVector) new BsonBinary(BsonBinarySubType.VECTOR, bsonEncodedVector).asVector(); + PackedBitBinaryVector decodedVector = (PackedBitBinaryVector) new BsonBinary(BsonBinarySubType.VECTOR, bsonEncodedVector).asVector(); // then assertEquals(expectedPackedBitVector, decodedVector); @@ -169,11 +169,11 @@ void shouldDecodePackedBitVector(final PackedBitVector expectedPackedBitVector, private static Stream providePackedBitVectors() { return Stream.of( arguments( - Vector.packedBitVector(new byte[]{(byte) 0, (byte) 255, (byte) 10}, (byte) 2), + BinaryVector.packedBitVector(new byte[]{(byte) 0, (byte) 255, (byte) 10}, (byte) 2), new byte[]{PACKED_BIT_DTYPE, 2, (byte) 0, (byte) 255, (byte) 10} ), arguments( - Vector.packedBitVector(new byte[0], (byte) 0), + BinaryVector.packedBitVector(new byte[0], (byte) 0), new byte[]{PACKED_BIT_DTYPE, 0} )); } diff --git a/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java b/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java index c9e22fcce7a..8e0f7c23c28 100644 --- a/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java +++ b/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java @@ -40,7 +40,7 @@ public class BsonBinaryWriterTest { - private static final byte FLOAT32_DTYPE = Vector.DataType.FLOAT32.getValue(); + private static final byte FLOAT32_DTYPE = BinaryVector.DataType.FLOAT32.getValue(); private static final int ZERO_PADDING = 0; private BsonBinaryWriter writer; diff --git a/bson/src/test/unit/org/bson/VectorTest.java b/bson/src/test/unit/org/bson/VectorTest.java deleted file mode 100644 index 36cc7156db6..00000000000 --- a/bson/src/test/unit/org/bson/VectorTest.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package org.bson; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class VectorTest { - - @Test - void shouldCreateInt8Vector() { - // given - byte[] data = {1, 2, 3, 4, 5}; - - // when - Int8Vector vector = Vector.int8Vector(data); - - // then - assertNotNull(vector); - assertEquals(Vector.DataType.INT8, vector.getDataType()); - assertArrayEquals(data, vector.getData()); - } - - @Test - void shouldThrowExceptionWhenCreatingInt8VectorWithNullData() { - // given - byte[] data = null; - - // when & Then - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Vector.int8Vector(data)); - assertEquals("data can not be null", exception.getMessage()); - } - - @Test - void shouldCreateFloat32Vector() { - // given - float[] data = {1.0f, 2.0f, 3.0f}; - - // when - Float32Vector vector = Vector.floatVector(data); - - // then - assertNotNull(vector); - assertEquals(Vector.DataType.FLOAT32, vector.getDataType()); - assertArrayEquals(data, vector.getData()); - } - - @Test - void shouldThrowExceptionWhenCreatingFloat32VectorWithNullData() { - // given - float[] data = null; - - // when & Then - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Vector.floatVector(data)); - assertEquals("data can not be null", exception.getMessage()); - } - - - @ParameterizedTest(name = "{index}: validPadding={0}") - @ValueSource(bytes = {0, 1, 2, 3, 4, 5, 6, 7}) - void shouldCreatePackedBitVector(final byte validPadding) { - // given - byte[] data = {(byte) 0b10101010, (byte) 0b01010101}; - - // when - PackedBitVector vector = Vector.packedBitVector(data, validPadding); - - // then - assertNotNull(vector); - assertEquals(Vector.DataType.PACKED_BIT, vector.getDataType()); - assertArrayEquals(data, vector.getData()); - assertEquals(validPadding, vector.getPadding()); - } - - @ParameterizedTest(name = "{index}: invalidPadding={0}") - @ValueSource(bytes = {-1, 8}) - void shouldThrowExceptionWhenPackedBitVectorHasInvalidPadding(final byte invalidPadding) { - // given - byte[] data = {(byte) 0b10101010}; - - // when & Then - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> - Vector.packedBitVector(data, invalidPadding)); - assertEquals("state should be: Padding must be between 0 and 7 bits. Provided padding: " + invalidPadding, exception.getMessage()); - } - - @Test - void shouldThrowExceptionWhenPackedBitVectorIsCreatedWithNullData() { - // given - byte[] data = null; - byte padding = 0; - - // when & Then - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> - Vector.packedBitVector(data, padding)); - assertEquals("data can not be null", exception.getMessage()); - } - - @Test - void shouldCreatePackedBitVectorWithZeroPaddingAndEmptyData() { - // given - byte[] data = new byte[0]; - byte padding = 0; - - // when - PackedBitVector vector = Vector.packedBitVector(data, padding); - - // then - assertNotNull(vector); - assertEquals(Vector.DataType.PACKED_BIT, vector.getDataType()); - assertArrayEquals(data, vector.getData()); - assertEquals(padding, vector.getPadding()); - } - - @Test - void shouldThrowExceptionWhenPackedBitVectorWithNonZeroPaddingAndEmptyData() { - // given - byte[] data = new byte[0]; - byte padding = 1; - - // when & Then - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> - Vector.packedBitVector(data, padding)); - assertEquals("state should be: Padding must be 0 if vector is empty. Provided padding: " + padding, exception.getMessage()); - } - - @Test - void shouldThrowExceptionWhenRetrievingInt8DataFromNonInt8Vector() { - // given - float[] data = {1.0f, 2.0f}; - Vector vector = Vector.floatVector(data); - - // when & Then - IllegalStateException exception = assertThrows(IllegalStateException.class, vector::asInt8Vector); - assertEquals("Expected vector data type INT8, but found FLOAT32", exception.getMessage()); - } - - @Test - void shouldThrowExceptionWhenRetrievingFloat32DataFromNonFloat32Vector() { - // given - byte[] data = {1, 2, 3}; - Vector vector = Vector.int8Vector(data); - - // when & Then - IllegalStateException exception = assertThrows(IllegalStateException.class, vector::asFloat32Vector); - assertEquals("Expected vector data type FLOAT32, but found INT8", exception.getMessage()); - } - - @Test - void shouldThrowExceptionWhenRetrievingPackedBitDataFromNonPackedBitVector() { - // given - float[] data = {1.0f, 2.0f}; - Vector vector = Vector.floatVector(data); - - // when & Then - IllegalStateException exception = assertThrows(IllegalStateException.class, vector::asPackedBitVector); - assertEquals("Expected vector data type PACKED_BIT, but found FLOAT32", exception.getMessage()); - } -} diff --git a/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java b/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java index 67c6b561aa5..7343707d5a7 100644 --- a/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java +++ b/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java @@ -23,7 +23,7 @@ import org.bson.BsonObjectId; import org.bson.ByteBufNIO; import org.bson.Document; -import org.bson.Vector; +import org.bson.BinaryVector; import org.bson.io.BasicOutputBuffer; import org.bson.io.BsonInput; import org.bson.io.ByteBufferBsonInput; @@ -81,9 +81,9 @@ public void testPrimitiveBSONTypeCodecs() throws IOException { doc.put("code", new Code("var i = 0")); doc.put("minkey", new MinKey()); doc.put("maxkey", new MaxKey()); - doc.put("vectorFloat", Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f})); - doc.put("vectorInt8", Vector.int8Vector(new byte[]{10, 20, 30, 40})); - doc.put("vectorPackedBit", Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3)); + doc.put("vectorFloat", BinaryVector.floatVector(new float[]{1.1f, 2.2f, 3.3f})); + doc.put("vectorInt8", BinaryVector.int8Vector(new byte[]{10, 20, 30, 40})); + doc.put("vectorPackedBit", BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3)); // doc.put("pattern", Pattern.compile("^hello")); // TODO: Pattern doesn't override equals method! doc.put("null", null); diff --git a/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy b/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy index 23c46fb7b0b..110c634ba5c 100644 --- a/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy @@ -17,10 +17,10 @@ package org.bson.codecs import org.bson.Document -import org.bson.Float32Vector -import org.bson.Int8Vector -import org.bson.PackedBitVector -import org.bson.Vector +import org.bson.Float32BinaryVector +import org.bson.Int8BinaryVector +import org.bson.PackedBitBinaryVector +import org.bson.BinaryVector import org.bson.codecs.configuration.CodecRegistries import org.bson.types.Binary import org.bson.types.Code @@ -62,10 +62,10 @@ class ValueCodecProviderSpecification extends Specification { provider.get(Short, registry) instanceof ShortCodec provider.get(byte[], registry) instanceof ByteArrayCodec provider.get(Float, registry) instanceof FloatCodec - provider.get(Vector, registry) instanceof VectorCodec - provider.get(Float32Vector, registry) instanceof Float32VectorCodec - provider.get(Int8Vector, registry) instanceof Int8VectorCodec - provider.get(PackedBitVector, registry) instanceof PackedBitVectorCodec + provider.get(BinaryVector, registry) instanceof VectorCodec + provider.get(Float32BinaryVector, registry) instanceof Float32VectorCodec + provider.get(Int8BinaryVector, registry) instanceof Int8VectorCodec + provider.get(PackedBitBinaryVector, registry) instanceof PackedBitVectorCodec provider.get(Binary, registry) instanceof BinaryCodec provider.get(MinKey, registry) instanceof MinKeyCodec diff --git a/bson/src/test/unit/org/bson/codecs/VectorCodecTest.java b/bson/src/test/unit/org/bson/codecs/VectorCodecTest.java deleted file mode 100644 index bf33af90cae..00000000000 --- a/bson/src/test/unit/org/bson/codecs/VectorCodecTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package org.bson.codecs; - -import org.bson.BsonBinary; -import org.bson.BsonBinaryReader; -import org.bson.BsonBinarySubType; -import org.bson.BsonBinaryWriter; -import org.bson.BsonDocument; -import org.bson.BsonInvalidOperationException; -import org.bson.BsonType; -import org.bson.BsonWriter; -import org.bson.ByteBufNIO; -import org.bson.Float32Vector; -import org.bson.Int8Vector; -import org.bson.PackedBitVector; -import org.bson.Vector; -import org.bson.io.BasicOutputBuffer; -import org.bson.io.ByteBufferBsonInput; -import org.bson.io.OutputBuffer; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.MethodSource; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.stream.Stream; - -import static org.bson.BsonHelper.toBson; -import static org.bson.assertions.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -class VectorCodecTest extends CodecTestCase { - - private static Stream provideVectorsAndCodecs() { - return Stream.of( - arguments(Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f}), new Float32VectorCodec(), Float32Vector.class), - arguments(Vector.int8Vector(new byte[]{10, 20, 30, 40}), new Int8VectorCodec(), Int8Vector.class), - arguments(Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new PackedBitVectorCodec(), PackedBitVector.class), - arguments(Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new VectorCodec(), Vector.class), - arguments(Vector.int8Vector(new byte[]{10, 20, 30, 40}), new VectorCodec(), Vector.class), - arguments(Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new VectorCodec(), Vector.class) - ); - } - - @ParameterizedTest - @MethodSource("provideVectorsAndCodecs") - void shouldEncodeVector(final Vector vectorToEncode, final Codec vectorCodec) throws IOException { - // given - BsonBinary bsonBinary = new BsonBinary(vectorToEncode); - byte[] encodedVector = bsonBinary.getData(); - ByteArrayOutputStream expectedStream = new ByteArrayOutputStream(); - // Total length of a Document (int 32). It is 0, because we do not expect - // codec to write the end of the document (that is when we back-patch the length of the document). - expectedStream.write(new byte[]{0, 0, 0, 0}); - // Bson type - expectedStream.write((byte) BsonType.BINARY.getValue()); - // Field name "b4" - expectedStream.write(new byte[]{98, 52, 0}); - // Total length of binary data (little-endian format) - expectedStream.write(new byte[]{(byte) encodedVector.length, 0, 0, 0}); - // Vector binary subtype - expectedStream.write(BsonBinarySubType.VECTOR.getValue()); - // Actual BSON binary data - expectedStream.write(encodedVector); - - OutputBuffer buffer = new BasicOutputBuffer(); - BsonWriter writer = new BsonBinaryWriter(buffer); - writer.writeStartDocument(); - writer.writeName("b4"); - - // when - vectorCodec.encode(writer, vectorToEncode, EncoderContext.builder().build()); - - // then - assertArrayEquals(expectedStream.toByteArray(), buffer.toByteArray()); - } - - @ParameterizedTest - @MethodSource("provideVectorsAndCodecs") - void shouldDecodeVector(final Vector vectorToDecode, final Codec vectorCodec) { - // given - OutputBuffer buffer = new BasicOutputBuffer(); - BsonWriter writer = new BsonBinaryWriter(buffer); - writer.writeStartDocument(); - writer.writeName("vector"); - writer.writeBinaryData(new BsonBinary(vectorToDecode)); - writer.writeEndDocument(); - - BsonBinaryReader reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(buffer.toByteArray())))); - reader.readStartDocument(); - - // when - Vector decodedVector = vectorCodec.decode(reader, DecoderContext.builder().build()); - - // then - assertDoesNotThrow(reader::readEndDocument); - assertNotNull(decodedVector); - assertEquals(vectorToDecode, decodedVector); - } - - - @ParameterizedTest - @EnumSource(value = BsonBinarySubType.class, mode = EnumSource.Mode.EXCLUDE, names = {"VECTOR"}) - void shouldThrowExceptionForInvalidSubType(final BsonBinarySubType subType) { - // given - BsonDocument document = new BsonDocument("name", new BsonBinary(subType.getValue(), new byte[]{})); - BsonBinaryReader reader = new BsonBinaryReader(toBson(document)); - reader.readStartDocument(); - - // when & then - Stream.of(new Float32VectorCodec(), new Int8VectorCodec(), new PackedBitVectorCodec()) - .forEach(codec -> { - BsonInvalidOperationException exception = assertThrows(BsonInvalidOperationException.class, () -> - codec.decode(reader, DecoderContext.builder().build())); - assertEquals("Expected vector binary subtype 9 but found: " + subType.getValue(), exception.getMessage()); - }); - } - - - @ParameterizedTest - @MethodSource("provideVectorsAndCodecs") - void shouldReturnCorrectEncoderClass(final Vector vector, - final Codec codec, - final Class expectedEncoderClass) { - // when - Class encoderClass = codec.getEncoderClass(); - - // then - assertEquals(expectedEncoderClass, encoderClass); - } -} diff --git a/bson/src/test/unit/org/bson/vector/VectorGenericBsonTest.java b/bson/src/test/unit/org/bson/vector/VectorGenericBsonTest.java deleted file mode 100644 index 64e84f6afc8..00000000000 --- a/bson/src/test/unit/org/bson/vector/VectorGenericBsonTest.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package org.bson.vector; - -import org.bson.BsonArray; -import org.bson.BsonBinary; -import org.bson.BsonDocument; -import org.bson.BsonString; -import org.bson.BsonValue; -import org.bson.Float32Vector; -import org.bson.PackedBitVector; -import org.bson.Vector; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import util.JsonPoweredTestHelper; - -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Stream; - -import static java.lang.String.format; -import static org.bson.BsonHelper.decodeToDocument; -import static org.bson.BsonHelper.encodeToHex; -import static org.bson.internal.vector.VectorHelper.determineVectorDType; -import static org.junit.Assert.assertThrows; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeFalse; - -/** - * See - * JSON-based tests that included in test resources. - */ -class VectorGenericBsonTest { - - private static final List TEST_NAMES_TO_IGNORE = Arrays.asList( - //NO API to set padding for floats available. - "FLOAT32 with padding", - //NO API to set padding for floats available. - "INT8 with padding", - //It is impossible to provide float inputs for INT8 in the API. - "INT8 with float inputs", - //It is impossible to provide float inputs for INT8. - "Underflow Vector PACKED_BIT", - //It is impossible to provide float inputs for PACKED_BIT in the API. - "Vector with float values PACKED_BIT", - //It is impossible to provide float inputs for INT8. - "Overflow Vector PACKED_BIT", - //It is impossible to overflow byte with values higher than 127 in the API. - "Overflow Vector INT8", - //It is impossible to underflow byte with values lower than -128 in the API. - "Underflow Vector INT8"); - - - @ParameterizedTest(name = "{0}") - @MethodSource("provideTestCases") - void shouldPassAllOutcomes(@SuppressWarnings("unused") final String description, - final BsonDocument testDefinition, final BsonDocument testCase) { - assumeFalse(TEST_NAMES_TO_IGNORE.contains(testCase.get("description").asString().getValue())); - - String testKey = testDefinition.getString("test_key").getValue(); - boolean isValidVector = testCase.getBoolean("valid").getValue(); - if (isValidVector) { - runValidTestCase(testKey, testCase); - } else { - runInvalidTestCase(testCase); - } - } - - private static void runInvalidTestCase(final BsonDocument testCase) { - BsonArray arrayVector = testCase.getArray("vector"); - byte expectedPadding = (byte) testCase.getInt32("padding").getValue(); - byte dtypeByte = Byte.decode(testCase.getString("dtype_hex").getValue()); - Vector.DataType expectedDType = determineVectorDType(dtypeByte); - - switch (expectedDType) { - case INT8: - byte[] expectedVectorData = toByteArray(arrayVector); - assertValidationException(assertThrows(RuntimeException.class, - () -> Vector.int8Vector(expectedVectorData))); - break; - case PACKED_BIT: - byte[] expectedVectorPackedBitData = toByteArray(arrayVector); - assertValidationException(assertThrows(RuntimeException.class, - () -> Vector.packedBitVector(expectedVectorPackedBitData, expectedPadding))); - break; - case FLOAT32: - float[] expectedFloatVector = toFloatArray(arrayVector); - assertValidationException(assertThrows(RuntimeException.class, () -> Vector.floatVector(expectedFloatVector))); - break; - default: - throw new IllegalArgumentException("Unsupported vector data type: " + expectedDType); - } - } - - private static void runValidTestCase(final String testKey, final BsonDocument testCase) { - String description = testCase.getString("description").getValue(); - byte dtypeByte = Byte.decode(testCase.getString("dtype_hex").getValue()); - - byte expectedPadding = (byte) testCase.getInt32("padding").getValue(); - Vector.DataType expectedDType = determineVectorDType(dtypeByte); - String expectedCanonicalBsonHex = testCase.getString("canonical_bson").getValue().toUpperCase(); - - BsonArray arrayVector = testCase.getArray("vector"); - BsonDocument actualDecodedDocument = decodeToDocument(expectedCanonicalBsonHex, description); - Vector actualVector = actualDecodedDocument.getBinary("vector").asVector(); - - switch (expectedDType) { - case INT8: - byte[] expectedVectorData = toByteArray(arrayVector); - byte[] actualVectorData = actualVector.asInt8Vector().getData(); - assertVectorDecoding( - expectedVectorData, - expectedDType, - actualVectorData, - actualVector); - - assertThatVectorCreationResultsInCorrectBinary(Vector.int8Vector(expectedVectorData), - testKey, - actualDecodedDocument, - expectedCanonicalBsonHex, - description); - break; - case PACKED_BIT: - PackedBitVector actualPackedBitVector = actualVector.asPackedBitVector(); - byte[] expectedVectorPackedBitData = toByteArray(arrayVector); - assertVectorDecoding( - expectedVectorPackedBitData, - expectedDType, expectedPadding, - actualPackedBitVector); - - assertThatVectorCreationResultsInCorrectBinary( - Vector.packedBitVector(expectedVectorPackedBitData, expectedPadding), - testKey, - actualDecodedDocument, - expectedCanonicalBsonHex, - description); - break; - case FLOAT32: - Float32Vector actualFloat32Vector = actualVector.asFloat32Vector(); - float[] expectedFloatVector = toFloatArray(arrayVector); - assertVectorDecoding( - expectedFloatVector, - expectedDType, - actualFloat32Vector); - assertThatVectorCreationResultsInCorrectBinary( - Vector.floatVector(expectedFloatVector), - testKey, - actualDecodedDocument, - expectedCanonicalBsonHex, - description); - break; - default: - throw new IllegalArgumentException("Unsupported vector data type: " + expectedDType); - } - } - - private static void assertValidationException(final RuntimeException runtimeException) { - assertTrue(runtimeException instanceof IllegalArgumentException || runtimeException instanceof IllegalStateException); - } - - private static void assertThatVectorCreationResultsInCorrectBinary(final Vector expectedVectorData, - final String testKey, - final BsonDocument actualDecodedDocument, - final String expectedCanonicalBsonHex, - final String description) { - BsonDocument documentToEncode = new BsonDocument(testKey, new BsonBinary(expectedVectorData)); - assertEquals(documentToEncode, actualDecodedDocument); - assertEquals(expectedCanonicalBsonHex, encodeToHex(documentToEncode), - format("Failed to create expected BSON for document with description '%s'", description)); - } - - private static void assertVectorDecoding(final byte[] expectedVectorData, - final Vector.DataType expectedDType, - final byte[] actualVectorData, - final Vector actualVector) { - Assertions.assertArrayEquals(actualVectorData, expectedVectorData, - () -> "Actual: " + Arrays.toString(actualVectorData) + " != Expected:" + Arrays.toString(expectedVectorData)); - assertEquals(expectedDType, actualVector.getDataType()); - } - - private static void assertVectorDecoding(final byte[] expectedVectorData, - final Vector.DataType expectedDType, - final byte expectedPadding, - final PackedBitVector actualVector) { - byte[] actualVectorData = actualVector.getData(); - assertVectorDecoding( - expectedVectorData, - expectedDType, - actualVectorData, - actualVector); - assertEquals(expectedPadding, actualVector.getPadding()); - } - - private static void assertVectorDecoding(final float[] expectedVectorData, - final Vector.DataType expectedDType, - final Float32Vector actualVector) { - float[] actualVectorArray = actualVector.getData(); - Assertions.assertArrayEquals(actualVectorArray, expectedVectorData, - () -> "Actual: " + Arrays.toString(actualVectorArray) + " != Expected:" + Arrays.toString(expectedVectorData)); - assertEquals(expectedDType, actualVector.getDataType()); - } - - private static byte[] toByteArray(final BsonArray arrayVector) { - byte[] bytes = new byte[arrayVector.size()]; - for (int i = 0; i < arrayVector.size(); i++) { - bytes[i] = (byte) arrayVector.get(i).asInt32().getValue(); - } - return bytes; - } - - private static float[] toFloatArray(final BsonArray arrayVector) { - float[] floats = new float[arrayVector.size()]; - for (int i = 0; i < arrayVector.size(); i++) { - BsonValue bsonValue = arrayVector.get(i); - if (bsonValue.isString()) { - floats[i] = parseFloat(bsonValue.asString()); - } else { - floats[i] = (float) arrayVector.get(i).asDouble().getValue(); - } - } - return floats; - } - - private static float parseFloat(final BsonString bsonValue) { - String floatValue = bsonValue.getValue(); - switch (floatValue) { - case "-inf": - return Float.NEGATIVE_INFINITY; - case "inf": - return Float.POSITIVE_INFINITY; - default: - return Float.parseFloat(floatValue); - } - } - - private static Stream provideTestCases() throws URISyntaxException, IOException { - List data = new ArrayList<>(); - for (File file : JsonPoweredTestHelper.getTestFiles("/bson-binary-vector")) { - BsonDocument testDocument = JsonPoweredTestHelper.getTestDocument(file); - for (BsonValue curValue : testDocument.getArray("tests", new BsonArray())) { - BsonDocument testCaseDocument = curValue.asDocument(); - data.add(Arguments.of(createTestCaseDescription(testDocument, testCaseDocument), testDocument, testCaseDocument)); - } - } - return data.stream(); - } - - private static String createTestCaseDescription(final BsonDocument testDocument, - final BsonDocument testCaseDocument) { - boolean isValidTestCase = testCaseDocument.getBoolean("valid").getValue(); - String fileDescription = testDocument.getString("description").getValue(); - String testDescription = testCaseDocument.getString("description").getValue(); - return "[Valid input: " + isValidTestCase + "] " + fileDescription + ": " + testDescription; - } -} diff --git a/driver-core/src/main/com/mongodb/client/model/Aggregates.java b/driver-core/src/main/com/mongodb/client/model/Aggregates.java index 7d6306cdd23..44283ccba04 100644 --- a/driver-core/src/main/com/mongodb/client/model/Aggregates.java +++ b/driver-core/src/main/com/mongodb/client/model/Aggregates.java @@ -37,7 +37,7 @@ import org.bson.BsonType; import org.bson.BsonValue; import org.bson.Document; -import org.bson.Vector; +import org.bson.BinaryVector; import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; @@ -972,7 +972,7 @@ public static Bson vectorSearch( * You may use the {@code $meta: "vectorSearchScore"} expression, e.g., via {@link Projections#metaVectorSearchScore(String)}, * to extract the relevance score assigned to each found document. * - * @param queryVector The {@linkplain Vector query vector}. The number of dimensions must match that of the {@code index}. + * @param queryVector The {@linkplain BinaryVector query vector}. The number of dimensions must match that of the {@code index}. * @param path The field to be searched. * @param index The name of the index to use. * @param limit The limit on the number of documents produced by the pipeline stage. @@ -981,12 +981,12 @@ public static Bson vectorSearch( * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch * @mongodb.atlas.manual atlas-search/scoring/ Scoring * @mongodb.server.release 6.0 - * @see Vector + * @see BinaryVector * @since 5.3 */ public static Bson vectorSearch( final FieldSearchPath path, - final Vector queryVector, + final BinaryVector queryVector, final String index, final long limit, final VectorSearchOptions options) { diff --git a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesVectorSearchIntegrationTest.java b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesVectorSearchIntegrationTest.java deleted file mode 100644 index 15def0f5d71..00000000000 --- a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesVectorSearchIntegrationTest.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package com.mongodb.client.model.search; - -import com.mongodb.MongoInterruptedException; -import com.mongodb.MongoNamespace; -import com.mongodb.client.model.Aggregates; -import com.mongodb.client.model.SearchIndexType; -import com.mongodb.client.test.CollectionHelper; -import com.mongodb.internal.operation.SearchIndexRequest; -import org.bson.BsonDocument; -import org.bson.Document; -import org.bson.Vector; -import org.bson.codecs.DocumentCodec; -import org.bson.conversions.Bson; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import static com.mongodb.ClusterFixture.isAtlasSearchTest; -import static com.mongodb.ClusterFixture.serverVersionAtLeast; -import static com.mongodb.client.model.Filters.and; -import static com.mongodb.client.model.Filters.eq; -import static com.mongodb.client.model.Filters.gt; -import static com.mongodb.client.model.Filters.gte; -import static com.mongodb.client.model.Filters.in; -import static com.mongodb.client.model.Filters.lt; -import static com.mongodb.client.model.Filters.lte; -import static com.mongodb.client.model.Filters.ne; -import static com.mongodb.client.model.Filters.nin; -import static com.mongodb.client.model.Filters.or; -import static com.mongodb.client.model.Projections.fields; -import static com.mongodb.client.model.Projections.metaVectorSearchScore; -import static com.mongodb.client.model.search.SearchPath.fieldPath; -import static com.mongodb.client.model.search.VectorSearchOptions.approximateVectorSearchOptions; -import static com.mongodb.client.model.search.VectorSearchOptions.exactVectorSearchOptions; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -class AggregatesVectorSearchIntegrationTest { - private static final String EXCEED_WAIT_ATTEMPTS_ERROR_MESSAGE = - "Exceeded maximum attempts waiting for Search Index creation in Atlas cluster. Index document: %s"; - - private static final String VECTOR_INDEX = "vector_search_index"; - private static final String VECTOR_FIELD_INT_8 = "int8Vector"; - private static final String VECTOR_FIELD_FLOAT_32 = "float32Vector"; - private static final String VECTOR_FIELD_LEGACY_DOUBLE_LIST = "legacyDoubleVector"; - private static final int LIMIT = 5; - private static final String FIELD_YEAR = "year"; - private static CollectionHelper collectionHelper; - private static final BsonDocument VECTOR_SEARCH_INDEX_DEFINITION = BsonDocument.parse( - "{" - + " fields: [" - + " {" - + " path: '" + VECTOR_FIELD_INT_8 + "'," - + " numDimensions: 5," - + " similarity: 'cosine'," - + " type: 'vector'," - + " }," - + " {" - + " path: '" + VECTOR_FIELD_FLOAT_32 + "'," - + " numDimensions: 5," - + " similarity: 'cosine'," - + " type: 'vector'," - + " }," - + " {" - + " path: '" + VECTOR_FIELD_LEGACY_DOUBLE_LIST + "'," - + " numDimensions: 5," - + " similarity: 'cosine'," - + " type: 'vector'," - + " }," - + " {" - + " path: '" + FIELD_YEAR + "'," - + " type: 'filter'," - + " }," - + " ]" - + "}"); - - @BeforeAll - static void beforeAll() { - assumeTrue(isAtlasSearchTest()); - assumeTrue(serverVersionAtLeast(6, 0)); - - collectionHelper = - new CollectionHelper<>(new DocumentCodec(), new MongoNamespace("javaVectorSearchTest", AggregatesVectorSearchIntegrationTest.class.getSimpleName())); - collectionHelper.drop(); - collectionHelper.insertDocuments( - new Document() - .append("_id", 0) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{0, 1, 2, 3, 4})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{0.0001, 1.12345, 2.23456, 3.34567, 4.45678}) - .append(FIELD_YEAR, 2016), - new Document() - .append("_id", 1) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{1, 2, 3, 4, 5})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{1.0001f, 2.12345f, 3.23456f, 4.34567f, 5.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{1.0001, 2.12345, 3.23456, 4.34567, 5.45678}) - .append(FIELD_YEAR, 2017), - new Document() - .append("_id", 2) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{2, 3, 4, 5, 6})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{2.0002f, 3.12345f, 4.23456f, 5.34567f, 6.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{2.0002, 3.12345, 4.23456, 5.34567, 6.45678}) - .append(FIELD_YEAR, 2018), - new Document() - .append("_id", 3) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{3, 4, 5, 6, 7})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{3.0003f, 4.12345f, 5.23456f, 6.34567f, 7.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{3.0003, 4.12345, 5.23456, 6.34567, 7.45678}) - .append(FIELD_YEAR, 2019), - new Document() - .append("_id", 4) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{4, 5, 6, 7, 8})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{4.0004f, 5.12345f, 6.23456f, 7.34567f, 8.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{4.0004, 5.12345, 6.23456, 7.34567, 8.45678}) - .append(FIELD_YEAR, 2020), - new Document() - .append("_id", 5) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{5, 6, 7, 8, 9})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{5.0005f, 6.12345f, 7.23456f, 8.34567f, 9.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{5.0005, 6.12345, 7.23456, 8.34567, 9.45678}) - .append(FIELD_YEAR, 2021), - new Document() - .append("_id", 6) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{6, 7, 8, 9, 10})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{6.0006f, 7.12345f, 8.23456f, 9.34567f, 10.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{6.0006, 7.12345, 8.23456, 9.34567, 10.45678}) - .append(FIELD_YEAR, 2022), - new Document() - .append("_id", 7) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{7, 8, 9, 10, 11})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{7.0007f, 8.12345f, 9.23456f, 10.34567f, 11.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{7.0007, 8.12345, 9.23456, 10.34567, 11.45678}) - .append(FIELD_YEAR, 2023), - new Document() - .append("_id", 8) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{8, 9, 10, 11, 12})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{8.0008f, 9.12345f, 10.23456f, 11.34567f, 12.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{8.0008, 9.12345, 10.23456, 11.34567, 12.45678}) - .append(FIELD_YEAR, 2024), - new Document() - .append("_id", 9) - .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{9, 10, 11, 12, 13})) - .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{9.0009f, 10.12345f, 11.23456f, 12.34567f, 13.45678f})) - .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{9.0009, 10.12345, 11.23456, 12.34567, 13.45678}) - .append(FIELD_YEAR, 2025) - ); - - collectionHelper.createSearchIndex( - new SearchIndexRequest(VECTOR_SEARCH_INDEX_DEFINITION, VECTOR_INDEX, - SearchIndexType.vectorSearch())); - awaitIndexCreation(); - } - - @AfterAll - static void afterAll() { - if (collectionHelper != null) { - collectionHelper.drop(); - } - } - - private static Stream provideSupportedVectors() { - return Stream.of( - arguments(Vector.int8Vector(new byte[]{0, 1, 2, 3, 4}), - // `multi` is used here only to verify that it is tolerated - fieldPath(VECTOR_FIELD_INT_8).multi("ignored"), - approximateVectorSearchOptions(LIMIT * 2)), - arguments(Vector.int8Vector(new byte[]{0, 1, 2, 3, 4}), - fieldPath(VECTOR_FIELD_INT_8), - approximateVectorSearchOptions(LIMIT * 2)), - - arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), - // `multi` is used here only to verify that it is tolerated - fieldPath(VECTOR_FIELD_FLOAT_32).multi("ignored"), - approximateVectorSearchOptions(LIMIT * 2)), - arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), - fieldPath(VECTOR_FIELD_FLOAT_32), - approximateVectorSearchOptions(LIMIT * 2)), - - arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), - // `multi` is used here only to verify that it is tolerated - fieldPath(VECTOR_FIELD_FLOAT_32).multi("ignored"), - exactVectorSearchOptions()), - arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), - fieldPath(VECTOR_FIELD_FLOAT_32), - exactVectorSearchOptions()), - - arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), - // `multi` is used here only to verify that it is tolerated - fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST).multi("ignored"), - exactVectorSearchOptions()), - arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), - fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST), - exactVectorSearchOptions()), - - arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), - // `multi` is used here only to verify that it is tolerated - fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST).multi("ignored"), - approximateVectorSearchOptions(LIMIT * 2)), - arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), - fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST), - approximateVectorSearchOptions(LIMIT * 2)) - ); - } - - @ParameterizedTest - @MethodSource("provideSupportedVectors") - void shouldSearchByVectorWithSearchScore(final Vector vector, - final FieldSearchPath fieldSearchPath, - final VectorSearchOptions vectorSearchOptions) { - //given - List pipeline = asList( - Aggregates.vectorSearch( - fieldSearchPath, - vector, - VECTOR_INDEX, LIMIT, - vectorSearchOptions), - Aggregates.project( - fields( - metaVectorSearchScore("vectorSearchScore") - )) - ); - - //when - List aggregate = collectionHelper.aggregate(pipeline); - - //then - Assertions.assertEquals(LIMIT, aggregate.size()); - assertScoreIsDecreasing(aggregate); - Document highestScoreDocument = aggregate.get(0); - assertEquals(1, highestScoreDocument.getDouble("vectorSearchScore")); - } - - @ParameterizedTest - @MethodSource("provideSupportedVectors") - void shouldSearchByVector(final Vector vector, - final FieldSearchPath fieldSearchPath, - final VectorSearchOptions vectorSearchOptions) { - //given - List pipeline = asList( - Aggregates.vectorSearch( - fieldSearchPath, - vector, - VECTOR_INDEX, LIMIT, - vectorSearchOptions) - ); - - //when - List aggregate = collectionHelper.aggregate(pipeline); - - //then - Assertions.assertEquals(LIMIT, aggregate.size()); - assertFalse( - aggregate.stream() - .anyMatch(document -> document.containsKey("vectorSearchScore")) - ); - } - - @ParameterizedTest - @MethodSource("provideSupportedVectors") - void shouldSearchByVectorWithFilter(final Vector vector, - final FieldSearchPath fieldSearchPath, - final VectorSearchOptions vectorSearchOptions) { - Consumer asserter = filter -> { - List pipeline = singletonList( - Aggregates.vectorSearch( - fieldSearchPath, vector, VECTOR_INDEX, 1, - vectorSearchOptions.filter(filter)) - ); - - List aggregate = collectionHelper.aggregate(pipeline); - Assertions.assertFalse(aggregate.isEmpty()); - }; - - assertAll( - () -> asserter.accept(lt("year", 2020)), - () -> asserter.accept(lte("year", 2020)), - () -> asserter.accept(eq("year", 2020)), - () -> asserter.accept(gte("year", 2016)), - () -> asserter.accept(gt("year", 2015)), - () -> asserter.accept(ne("year", 2016)), - () -> asserter.accept(in("year", 2000, 2024)), - () -> asserter.accept(nin("year", 2000, 2024)), - () -> asserter.accept(and(gte("year", 2015), lte("year", 2017))), - () -> asserter.accept(or(eq("year", 2015), eq("year", 2017))) - ); - } - - private static void assertScoreIsDecreasing(final List aggregate) { - double previousScore = Integer.MAX_VALUE; - for (Document document : aggregate) { - Double vectorSearchScore = document.getDouble("vectorSearchScore"); - assertTrue(vectorSearchScore > 0, "Expected positive score"); - assertTrue(vectorSearchScore < previousScore, "Expected decreasing score"); - previousScore = vectorSearchScore; - } - } - - private static void awaitIndexCreation() { - int attempts = 10; - Optional searchIndex = Optional.empty(); - - while (attempts-- > 0) { - searchIndex = collectionHelper.listSearchIndex(VECTOR_INDEX); - if (searchIndex.filter(document -> document.getBoolean("queryable")) - .isPresent()) { - return; - } - - try { - TimeUnit.SECONDS.sleep(5); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new MongoInterruptedException(null, e); - } - } - - searchIndex.ifPresent(document -> - Assertions.fail(format(EXCEED_WAIT_ATTEMPTS_ERROR_MESSAGE, document.toJson()))); - Assertions.fail(format(EXCEED_WAIT_ATTEMPTS_ERROR_MESSAGE, "null")); - } -} diff --git a/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy index 1370a9ef25a..f78aefd51b4 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy @@ -23,7 +23,7 @@ import com.mongodb.client.model.search.SearchOperator import org.bson.BsonDocument import org.bson.BsonInt32 import org.bson.Document -import org.bson.Vector +import org.bson.BinaryVector import org.bson.conversions.Bson import spock.lang.IgnoreIf import spock.lang.Specification @@ -879,9 +879,9 @@ class AggregatesSpecification extends Specification { where: vector | queryVector - Vector.int8Vector([127, 7] as byte[]) | '{"$binary": {"base64": "AwB/Bw==", "subType": "09"}}' - Vector.floatVector([127.0f, 7.0f] as float[]) | '{"$binary": {"base64": "JwAAAP5CAADgQA==", "subType": "09"}}' - Vector.packedBitVector([127, 7] as byte[], (byte) 0) | '{"$binary": {"base64": "EAB/Bw==", "subType": "09"}}' + BinaryVector.int8Vector([127, 7] as byte[]) | '{"$binary": {"base64": "AwB/Bw==", "subType": "09"}}' + BinaryVector.floatVector([127.0f, 7.0f] as float[]) | '{"$binary": {"base64": "JwAAAP5CAADgQA==", "subType": "09"}}' + BinaryVector.packedBitVector([127, 7] as byte[], (byte) 0) | '{"$binary": {"base64": "EAB/Bw==", "subType": "09"}}' [1.0d, 2.0d] | "[1.0, 2.0]" } @@ -913,8 +913,8 @@ class AggregatesSpecification extends Specification { where: vector | queryVector - Vector.int8Vector([127, 7] as byte[]) | '{"$binary": {"base64": "AwB/Bw==", "subType": "09"}}' - Vector.floatVector([127.0f, 7.0f] as float[]) | '{"$binary": {"base64": "JwAAAP5CAADgQA==", "subType": "09"}}' + BinaryVector.int8Vector([127, 7] as byte[]) | '{"$binary": {"base64": "AwB/Bw==", "subType": "09"}}' + BinaryVector.floatVector([127.0f, 7.0f] as float[]) | '{"$binary": {"base64": "JwAAAP5CAADgQA==", "subType": "09"}}' [1.0d, 2.0d] | "[1.0, 2.0]" } diff --git a/driver-core/src/test/unit/com/mongodb/client/model/search/VectorSearchOptionsTest.java b/driver-core/src/test/unit/com/mongodb/client/model/search/VectorSearchOptionsTest.java deleted file mode 100644 index 190347dc1fe..00000000000 --- a/driver-core/src/test/unit/com/mongodb/client/model/search/VectorSearchOptionsTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2008-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. - */ -package com.mongodb.client.model.search; - -import com.mongodb.client.model.Filters; -import org.bson.BsonBoolean; -import org.bson.BsonDocument; -import org.bson.BsonInt64; -import org.bson.BsonString; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -final class VectorSearchOptionsTest { - @Test - void approximateVectorSearchOptions() { - assertEquals( - new BsonDocument().append("numCandidates", new BsonInt64(1)), - VectorSearchOptions.approximateVectorSearchOptions(1) - .toBsonDocument() - ); - } - - @Test - void exactVectorSearchOptions() { - assertEquals( - new BsonDocument().append("exact", new BsonBoolean(true)), - VectorSearchOptions.exactVectorSearchOptions() - .toBsonDocument() - ); - } - - @Test - void option() { - assertEquals( - VectorSearchOptions.approximateVectorSearchOptions(1) - .filter(Filters.lt("fieldName", 1)) - .toBsonDocument(), - VectorSearchOptions.approximateVectorSearchOptions(1) - .option("filter", Filters.lt("fieldName", 1)) - .toBsonDocument()); - } - - @Test - void filterApproximate() { - assertEquals( - new BsonDocument() - .append("filter", Filters.lt("fieldName", 1).toBsonDocument()) - .append("numCandidates", new BsonInt64(1)), - VectorSearchOptions.approximateVectorSearchOptions(1) - .filter(Filters.lt("fieldName", 1)) - .toBsonDocument() - ); - } - - @Test - void filterExact() { - assertEquals( - new BsonDocument() - .append("filter", Filters.lt("fieldName", 1).toBsonDocument()) - .append("exact", new BsonBoolean(true)), - VectorSearchOptions.exactVectorSearchOptions() - .filter(Filters.lt("fieldName", 1)) - .toBsonDocument() - ); - } - - @Test - void optionsApproximate() { - assertEquals( - new BsonDocument() - .append("name", new BsonString("value")) - .append("filter", Filters.lt("fieldName", 1).toBsonDocument()) - .append("numCandidates", new BsonInt64(1)), - VectorSearchOptions.approximateVectorSearchOptions(1) - .option("name", "value") - .filter(Filters.lt("fieldName", 0)) - .option("filter", Filters.lt("fieldName", 1)) - .option("numCandidates", new BsonInt64(1)) - .toBsonDocument() - ); - } - - @Test - void optionsExact() { - assertEquals( - new BsonDocument() - .append("name", new BsonString("value")) - .append("filter", Filters.lt("fieldName", 1).toBsonDocument()) - .append("exact", new BsonBoolean(true)), - VectorSearchOptions.exactVectorSearchOptions() - .option("name", "value") - .filter(Filters.lt("fieldName", 0)) - .option("filter", Filters.lt("fieldName", 1)) - .option("exact", new BsonBoolean(true)) - .toBsonDocument() - ); - } - - @Test - void approximateVectorSearchOptionsIsUnmodifiable() { - String expected = VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().toJson(); - VectorSearchOptions.approximateVectorSearchOptions(1).option("name", "value"); - assertEquals(expected, VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().toJson()); - } - - @Test - void approximateVectorSearchOptionsIsImmutable() { - String expected = VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().toJson(); - VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().append("name", new BsonString("value")); - assertEquals(expected, VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().toJson()); - } -} diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/VectorFunctionalTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/VectorFunctionalTest.java deleted file mode 100644 index f5b8e63f8c3..00000000000 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/VectorFunctionalTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package com.mongodb.reactivestreams.client.vector; - -import com.mongodb.MongoClientSettings; -import com.mongodb.client.MongoClient; -import com.mongodb.client.vector.AbstractVectorFunctionalTest; -import com.mongodb.reactivestreams.client.MongoClients; -import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; - -public class VectorFunctionalTest extends AbstractVectorFunctionalTest { - @Override - protected MongoClient getMongoClient(final MongoClientSettings settings) { - return new SyncMongoClient(MongoClients.create(settings)); - } -} diff --git a/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractVectorFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractVectorFunctionalTest.java deleted file mode 100644 index c3edf6983da..00000000000 --- a/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractVectorFunctionalTest.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package com.mongodb.client.vector; - -import com.mongodb.MongoClientSettings; -import com.mongodb.ReadConcern; -import com.mongodb.ReadPreference; -import com.mongodb.WriteConcern; -import com.mongodb.client.Fixture; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.model.OperationTest; -import org.bson.BsonBinary; -import org.bson.BsonBinarySubType; -import org.bson.BsonInvalidOperationException; -import org.bson.Document; -import org.bson.Float32Vector; -import org.bson.Int8Vector; -import org.bson.PackedBitVector; -import org.bson.Vector; -import org.bson.codecs.configuration.CodecRegistry; -import org.bson.codecs.pojo.PojoCodecProvider; -import org.bson.types.Binary; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry; -import static org.bson.Vector.DataType.FLOAT32; -import static org.bson.Vector.DataType.INT8; -import static org.bson.Vector.DataType.PACKED_BIT; -import static org.bson.codecs.configuration.CodecRegistries.fromProviders; -import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public abstract class AbstractVectorFunctionalTest extends OperationTest { - - private static final byte VECTOR_SUBTYPE = BsonBinarySubType.VECTOR.getValue(); - private static final String FIELD_VECTOR = "vector"; - private static final CodecRegistry CODEC_REGISTRY = fromRegistries(getDefaultCodecRegistry(), - fromProviders(PojoCodecProvider - .builder() - .automatic(true).build())); - private MongoCollection documentCollection; - - private MongoClient mongoClient; - - @BeforeEach - public void setUp() { - super.beforeEach(); - mongoClient = getMongoClient(getMongoClientSettingsBuilder() - .codecRegistry(CODEC_REGISTRY) - .build()); - documentCollection = mongoClient - .getDatabase(getDatabaseName()) - .getCollection(getCollectionName()); - } - - @AfterEach - @SuppressWarnings("try") - public void afterEach() { - try (MongoClient ignore = mongoClient) { - super.afterEach(); - } - } - - private static MongoClientSettings.Builder getMongoClientSettingsBuilder() { - return Fixture.getMongoClientSettingsBuilder() - .readConcern(ReadConcern.MAJORITY) - .writeConcern(WriteConcern.MAJORITY) - .readPreference(ReadPreference.primary()); - } - - protected abstract MongoClient getMongoClient(MongoClientSettings settings); - - @ParameterizedTest - @ValueSource(bytes = {-1, 1, 2, 3, 4, 5, 6, 7, 8}) - void shouldThrowExceptionForInvalidPackedBitArrayPaddingWhenDecodeEmptyVector(final byte invalidPadding) { - //given - Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{PACKED_BIT.getValue(), invalidPadding}); - documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector)); - - // when & then - BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> { - findExactlyOne(documentCollection) - .get(FIELD_VECTOR, Vector.class); - }); - assertEquals("Padding must be 0 if vector is empty, but found: " + invalidPadding, exception.getMessage()); - } - - @ParameterizedTest - @ValueSource(bytes = {-1, 1}) - void shouldThrowExceptionForInvalidFloat32Padding(final byte invalidPadding) { - // given - Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{FLOAT32.getValue(), invalidPadding, 10, 20, 30, 40}); - documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector)); - - // when & then - BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> { - findExactlyOne(documentCollection) - .get(FIELD_VECTOR, Vector.class); - }); - assertEquals("Padding must be 0 for FLOAT32 data type, but found: " + invalidPadding, exception.getMessage()); - } - - @ParameterizedTest - @ValueSource(bytes = {-1, 1}) - void shouldThrowExceptionForInvalidInt8Padding(final byte invalidPadding) { - // given - Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{INT8.getValue(), invalidPadding, 10, 20, 30, 40}); - documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector)); - - // when & then - BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> { - findExactlyOne(documentCollection) - .get(FIELD_VECTOR, Vector.class); - }); - assertEquals("Padding must be 0 for INT8 data type, but found: " + invalidPadding, exception.getMessage()); - } - - @ParameterizedTest - @ValueSource(bytes = {-1, 8}) - void shouldThrowExceptionForInvalidPackedBitPadding(final byte invalidPadding) { - // given - Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{PACKED_BIT.getValue(), invalidPadding, 10, 20, 30, 40}); - documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector)); - - // when & then - BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> { - findExactlyOne(documentCollection) - .get(FIELD_VECTOR, Vector.class); - }); - assertEquals("Padding must be between 0 and 7 bits, but found: " + invalidPadding, exception.getMessage()); - } - - private static Stream provideValidVectors() { - return Stream.of( - Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f}), - Vector.int8Vector(new byte[]{10, 20, 30, 40}), - Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3) - ); - } - - @ParameterizedTest - @MethodSource("provideValidVectors") - void shouldStoreAndRetrieveValidVector(final Vector expectedVector) { - // Given - Document documentToInsert = new Document(FIELD_VECTOR, expectedVector) - .append("otherField", 1); // to test that the next field is not affected - documentCollection.insertOne(documentToInsert); - - // when & then - Vector actualVector = findExactlyOne(documentCollection) - .get(FIELD_VECTOR, Vector.class); - - assertEquals(expectedVector, actualVector); - } - - @ParameterizedTest - @MethodSource("provideValidVectors") - void shouldStoreAndRetrieveValidVectorWithBsonBinary(final Vector expectedVector) { - // Given - Document documentToInsert = new Document(FIELD_VECTOR, new BsonBinary(expectedVector)); - documentCollection.insertOne(documentToInsert); - - // when & then - Vector actualVector = findExactlyOne(documentCollection) - .get(FIELD_VECTOR, Vector.class); - - assertEquals(actualVector, actualVector); - } - - @Test - void shouldStoreAndRetrieveValidVectorWithFloatVectorPojo() { - // given - MongoCollection floatVectorPojoMongoCollection = mongoClient - .getDatabase(getDatabaseName()) - .getCollection(getCollectionName()).withDocumentClass(FloatVectorPojo.class); - Float32Vector vector = Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f}); - - // whe - floatVectorPojoMongoCollection.insertOne(new FloatVectorPojo(vector)); - FloatVectorPojo floatVectorPojo = floatVectorPojoMongoCollection.find().first(); - - // then - Assertions.assertNotNull(floatVectorPojo); - assertEquals(vector, floatVectorPojo.getVector()); - } - - @Test - void shouldStoreAndRetrieveValidVectorWithInt8VectorPojo() { - // given - MongoCollection floatVectorPojoMongoCollection = mongoClient - .getDatabase(getDatabaseName()) - .getCollection(getCollectionName()).withDocumentClass(Int8VectorPojo.class); - Int8Vector vector = Vector.int8Vector(new byte[]{10, 20, 30, 40}); - - // when - floatVectorPojoMongoCollection.insertOne(new Int8VectorPojo(vector)); - Int8VectorPojo int8VectorPojo = floatVectorPojoMongoCollection.find().first(); - - // then - Assertions.assertNotNull(int8VectorPojo); - assertEquals(vector, int8VectorPojo.getVector()); - } - - @Test - void shouldStoreAndRetrieveValidVectorWithPackedBitVectorPojo() { - // given - MongoCollection floatVectorPojoMongoCollection = mongoClient - .getDatabase(getDatabaseName()) - .getCollection(getCollectionName()).withDocumentClass(PackedBitVectorPojo.class); - - PackedBitVector vector = Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3); - - // when - floatVectorPojoMongoCollection.insertOne(new PackedBitVectorPojo(vector)); - PackedBitVectorPojo packedBitVectorPojo = floatVectorPojoMongoCollection.find().first(); - - // then - Assertions.assertNotNull(packedBitVectorPojo); - assertEquals(vector, packedBitVectorPojo.getVector()); - } - - @ParameterizedTest - @MethodSource("provideValidVectors") - void shouldStoreAndRetrieveValidVectorWithGenericVectorPojo(final Vector actualVector) { - // given - MongoCollection floatVectorPojoMongoCollection = mongoClient - .getDatabase(getDatabaseName()) - .getCollection(getCollectionName()).withDocumentClass(VectorPojo.class); - - // when - floatVectorPojoMongoCollection.insertOne(new VectorPojo(actualVector)); - VectorPojo vectorPojo = floatVectorPojoMongoCollection.find().first(); - - //then - Assertions.assertNotNull(vectorPojo); - assertEquals(actualVector, vectorPojo.getVector()); - } - - private Document findExactlyOne(final MongoCollection collection) { - List documents = new ArrayList<>(); - collection.find().into(documents); - assertEquals(1, documents.size(), "Expected exactly one document, but found: " + documents.size()); - return documents.get(0); - } - - public static class VectorPojo { - private Vector vector; - - public VectorPojo() { - } - - public VectorPojo(final Vector vector) { - this.vector = vector; - } - - public Vector getVector() { - return vector; - } - - public void setVector(final Vector vector) { - this.vector = vector; - } - } - - public static class Int8VectorPojo { - private Int8Vector vector; - - public Int8VectorPojo() { - } - - public Int8VectorPojo(final Int8Vector vector) { - this.vector = vector; - } - - public Vector getVector() { - return vector; - } - - public void setVector(final Int8Vector vector) { - this.vector = vector; - } - } - - public static class PackedBitVectorPojo { - private PackedBitVector vector; - - public PackedBitVectorPojo() { - } - - public PackedBitVectorPojo(final PackedBitVector vector) { - this.vector = vector; - } - - public Vector getVector() { - return vector; - } - - public void setVector(final PackedBitVector vector) { - this.vector = vector; - } - } - - public static class FloatVectorPojo { - private Float32Vector vector; - - public FloatVectorPojo() { - } - - public FloatVectorPojo(final Float32Vector vector) { - this.vector = vector; - } - - public Vector getVector() { - return vector; - } - - public void setVector(final Float32Vector vector) { - this.vector = vector; - } - } -} diff --git a/driver-sync/src/test/functional/com/mongodb/client/vector/VectorFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/vector/VectorFunctionalTest.java deleted file mode 100644 index 63d756a8f35..00000000000 --- a/driver-sync/src/test/functional/com/mongodb/client/vector/VectorFunctionalTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2008-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. - */ - -package com.mongodb.client.vector; - -import com.mongodb.MongoClientSettings; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; - -public class VectorFunctionalTest extends AbstractVectorFunctionalTest { - @Override - protected MongoClient getMongoClient(final MongoClientSettings settings) { - return MongoClients.create(settings); - } -} From 94ede08a5c4d59d350e59d9d074ca70ceb6f9400 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 7 Jan 2025 13:44:46 -0800 Subject: [PATCH 2/4] Add missing renamed classes. JAVA-5710 --- bson/src/main/org/bson/BinaryVector.java | 201 ++++++++++ .../main/org/bson/Float32BinaryVector.java | 79 ++++ bson/src/main/org/bson/Int8BinaryVector.java | 80 ++++ .../main/org/bson/PackedBitBinaryVector.java | 101 +++++ .../test/unit/org/bson/BinaryVectorTest.java | 179 +++++++++ .../bson/codecs/BinaryVectorCodecTest.java | 152 ++++++++ .../vector/BinaryVectorGenericBsonTest.java | 276 ++++++++++++++ ...atesBinaryVectorSearchIntegrationTest.java | 353 ++++++++++++++++++ .../search/BinaryVectorSearchOptionsTest.java | 126 +++++++ .../vector/BinaryVectorFunctionalTest.java | 30 ++ .../AbstractBinaryVectorFunctionalTest.java | 346 +++++++++++++++++ .../vector/BinaryVectorFunctionalTest.java | 28 ++ 12 files changed, 1951 insertions(+) create mode 100644 bson/src/main/org/bson/BinaryVector.java create mode 100644 bson/src/main/org/bson/Float32BinaryVector.java create mode 100644 bson/src/main/org/bson/Int8BinaryVector.java create mode 100644 bson/src/main/org/bson/PackedBitBinaryVector.java create mode 100644 bson/src/test/unit/org/bson/BinaryVectorTest.java create mode 100644 bson/src/test/unit/org/bson/codecs/BinaryVectorCodecTest.java create mode 100644 bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesBinaryVectorSearchIntegrationTest.java create mode 100644 driver-core/src/test/unit/com/mongodb/client/model/search/BinaryVectorSearchOptionsTest.java create mode 100644 driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/BinaryVectorFunctionalTest.java create mode 100644 driver-sync/src/test/functional/com/mongodb/client/vector/AbstractBinaryVectorFunctionalTest.java create mode 100644 driver-sync/src/test/functional/com/mongodb/client/vector/BinaryVectorFunctionalTest.java diff --git a/bson/src/main/org/bson/BinaryVector.java b/bson/src/main/org/bson/BinaryVector.java new file mode 100644 index 00000000000..4cf01c76dab --- /dev/null +++ b/bson/src/main/org/bson/BinaryVector.java @@ -0,0 +1,201 @@ +/* + * Copyright 2008-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. + */ + +package org.bson; + +import static org.bson.assertions.Assertions.isTrueArgument; +import static org.bson.assertions.Assertions.notNull; + +/** + * Represents a vector that is stored and retrieved using the BSON Binary Subtype 9 format. + * This class supports multiple vector {@link DataType}'s and provides static methods to create + * vectors. + *

+ * Vectors are densely packed arrays of numbers, all the same type, which are stored efficiently + * in BSON using a binary format. + *

+ * NOTE: This class should be treated as sealed: it must not be extended or implemented by consumers of the library. + * + * @mongodb.server.release 6.0 + * @see BsonBinary + * @since 5.3 + */ +public abstract class BinaryVector { + private final DataType dataType; + + BinaryVector(final DataType dataType) { + this.dataType = dataType; + } + + /** + * Creates a vector with the {@link DataType#PACKED_BIT} data type. + *

+ * A {@link DataType#PACKED_BIT} vector is a binary quantized vector where each element of a vector is represented by a single bit (0 or 1). Each byte + * can hold up to 8 bits (vector elements). The padding parameter is used to specify how many least-significant bits in the final byte + * should be ignored.

+ * + *

For example, a vector with two bytes and a padding of 4 would have the following structure:

+ *
+     * Byte 1: 238 (binary: 11101110)
+     * Byte 2: 224 (binary: 11100000)
+     * Padding: 4 (ignore the last 4 bits in Byte 2)
+     * Resulting vector: 12 bits: 111011101110
+     * 
+ *

+ * NOTE: The byte array `data` is not copied; changes to the provided array will be reflected + * in the created {@link PackedBitBinaryVector} instance. + * + * @param data The byte array representing the packed bit vector data. Each byte can store 8 bits. + * @param padding The number of least-significant bits (0 to 7) to ignore in the final byte of the vector data. + * @return A {@link PackedBitBinaryVector} instance with the {@link DataType#PACKED_BIT} data type. + * @throws IllegalArgumentException If the padding value is greater than 7. + */ + public static PackedBitBinaryVector packedBitVector(final byte[] data, final byte padding) { + notNull("data", data); + isTrueArgument("Padding must be between 0 and 7 bits. Provided padding: " + padding, padding >= 0 && padding <= 7); + isTrueArgument("Padding must be 0 if vector is empty. Provided padding: " + padding, padding == 0 || data.length > 0); + return new PackedBitBinaryVector(data, padding); + } + + /** + * Creates a vector with the {@link DataType#INT8} data type. + * + *

A {@link DataType#INT8} vector is a vector of 8-bit signed integers where each byte in the vector represents an element of a vector, + * with values in the range [-128, 127].

+ *

+ * NOTE: The byte array `data` is not copied; changes to the provided array will be reflected + * in the created {@link Int8BinaryVector} instance. + * + * @param data The byte array representing the {@link DataType#INT8} vector data. + * @return A {@link Int8BinaryVector} instance with the {@link DataType#INT8} data type. + */ + public static Int8BinaryVector int8Vector(final byte[] data) { + notNull("data", data); + return new Int8BinaryVector(data); + } + + /** + * Creates a vector with the {@link DataType#FLOAT32} data type. + *

+ * A {@link DataType#FLOAT32} vector is a vector of floating-point numbers, where each element in the vector is a float.

+ *

+ * NOTE: The float array `data` is not copied; changes to the provided array will be reflected + * in the created {@link Float32BinaryVector} instance. + * + * @param data The float array representing the {@link DataType#FLOAT32} vector data. + * @return A {@link Float32BinaryVector} instance with the {@link DataType#FLOAT32} data type. + */ + public static Float32BinaryVector floatVector(final float[] data) { + notNull("data", data); + return new Float32BinaryVector(data); + } + + /** + * Returns the {@link PackedBitBinaryVector}. + * + * @return {@link PackedBitBinaryVector}. + * @throws IllegalStateException if this vector is not of type {@link DataType#PACKED_BIT}. Use {@link #getDataType()} to check the vector + * type before calling this method. + */ + public PackedBitBinaryVector asPackedBitVector() { + ensureType(DataType.PACKED_BIT); + return (PackedBitBinaryVector) this; + } + + /** + * Returns the {@link Int8BinaryVector}. + * + * @return {@link Int8BinaryVector}. + * @throws IllegalStateException if this vector is not of type {@link DataType#INT8}. Use {@link #getDataType()} to check the vector + * type before calling this method. + */ + public Int8BinaryVector asInt8Vector() { + ensureType(DataType.INT8); + return (Int8BinaryVector) this; + } + + /** + * Returns the {@link Float32BinaryVector}. + * + * @return {@link Float32BinaryVector}. + * @throws IllegalStateException if this vector is not of type {@link DataType#FLOAT32}. Use {@link #getDataType()} to check the vector + * type before calling this method. + */ + public Float32BinaryVector asFloat32Vector() { + ensureType(DataType.FLOAT32); + return (Float32BinaryVector) this; + } + + /** + * Returns {@link DataType} of the vector. + * + * @return the data type of the vector. + */ + public DataType getDataType() { + return this.dataType; + } + + + private void ensureType(final DataType expected) { + if (this.dataType != expected) { + throw new IllegalStateException("Expected vector data type " + expected + ", but found " + this.dataType); + } + } + + /** + * Represents the data type (dtype) of a vector. + *

+ * Each dtype determines how the data in the vector is stored, including how many bits are used to represent each element + * in the vector. + * + * @mongodb.server.release 6.0 + * @since 5.3 + */ + public enum DataType { + /** + * An INT8 vector is a vector of 8-bit signed integers. The vector is stored as an array of bytes, where each byte + * represents a signed integer in the range [-128, 127]. + */ + INT8((byte) 0x03), + /** + * A FLOAT32 vector is a vector of 32-bit floating-point numbers, where each element in the vector is a float. + */ + FLOAT32((byte) 0x27), + /** + * A PACKED_BIT vector is a binary quantized vector where each element of a vector is represented by a single bit (0 or 1). + * Each byte can hold up to 8 bits (vector elements). + */ + PACKED_BIT((byte) 0x10); + + private final byte value; + + DataType(final byte value) { + this.value = value; + } + + /** + * Returns the byte value associated with this {@link DataType}. + * + *

This value is used in the BSON binary format to indicate the data type of the vector.

+ * + * @return the byte value representing the {@link DataType}. + */ + public byte getValue() { + return value; + } + } +} + diff --git a/bson/src/main/org/bson/Float32BinaryVector.java b/bson/src/main/org/bson/Float32BinaryVector.java new file mode 100644 index 00000000000..37d1b8abb6e --- /dev/null +++ b/bson/src/main/org/bson/Float32BinaryVector.java @@ -0,0 +1,79 @@ +/* + * Copyright 2008-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. + */ + +package org.bson; + +import java.util.Arrays; + +import static org.bson.assertions.Assertions.assertNotNull; + +/** + * Represents a vector of 32-bit floating-point numbers, where each element in the vector is a float. + *

+ * The {@link Float32BinaryVector} is used to store and retrieve data efficiently using the BSON Binary Subtype 9 format. + * + * @mongodb.server.release 6.0 + * @see BinaryVector#floatVector(float[]) + * @see BsonBinary#BsonBinary(BinaryVector) + * @see BsonBinary#asVector() + * @since 5.3 + */ +public final class Float32BinaryVector extends BinaryVector { + + private final float[] data; + + Float32BinaryVector(final float[] vectorData) { + super(DataType.FLOAT32); + this.data = assertNotNull(vectorData); + } + + /** + * Retrieve the underlying float array representing this {@link Float32BinaryVector}, where each float + * represents an element of a vector. + *

+ * NOTE: The underlying float array is not copied; changes to the returned array will be reflected in this instance. + * + * @return the underlying float array representing this {@link Float32BinaryVector} vector. + */ + public float[] getData() { + return assertNotNull(data); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Float32BinaryVector that = (Float32BinaryVector) o; + return Arrays.equals(data, that.data); + } + + @Override + public int hashCode() { + return Arrays.hashCode(data); + } + + @Override + public String toString() { + return "Float32Vector{" + + "data=" + Arrays.toString(data) + + ", dataType=" + getDataType() + + '}'; + } +} diff --git a/bson/src/main/org/bson/Int8BinaryVector.java b/bson/src/main/org/bson/Int8BinaryVector.java new file mode 100644 index 00000000000..a851aff94ff --- /dev/null +++ b/bson/src/main/org/bson/Int8BinaryVector.java @@ -0,0 +1,80 @@ +/* + * Copyright 2008-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. + */ + +package org.bson; + +import java.util.Arrays; +import java.util.Objects; + +import static org.bson.assertions.Assertions.assertNotNull; + +/** + * Represents a vector of 8-bit signed integers, where each element in the vector is a byte. + *

+ * The {@link Int8BinaryVector} is used to store and retrieve data efficiently using the BSON Binary Subtype 9 format. + * + * @mongodb.server.release 6.0 + * @see BinaryVector#int8Vector(byte[]) + * @see BsonBinary#BsonBinary(BinaryVector) + * @see BsonBinary#asVector() + * @since 5.3 + */ +public final class Int8BinaryVector extends BinaryVector { + + private byte[] data; + + Int8BinaryVector(final byte[] data) { + super(DataType.INT8); + this.data = assertNotNull(data); + } + + /** + * Retrieve the underlying byte array representing this {@link Int8BinaryVector} vector, where each byte represents + * an element of a vector. + *

+ * NOTE: The underlying byte array is not copied; changes to the returned array will be reflected in this instance. + * + * @return the underlying byte array representing this {@link Int8BinaryVector} vector. + */ + public byte[] getData() { + return assertNotNull(data); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Int8BinaryVector that = (Int8BinaryVector) o; + return Objects.deepEquals(data, that.data); + } + + @Override + public int hashCode() { + return Arrays.hashCode(data); + } + + @Override + public String toString() { + return "Int8Vector{" + + "data=" + Arrays.toString(data) + + ", dataType=" + getDataType() + + '}'; + } +} diff --git a/bson/src/main/org/bson/PackedBitBinaryVector.java b/bson/src/main/org/bson/PackedBitBinaryVector.java new file mode 100644 index 00000000000..28d00174036 --- /dev/null +++ b/bson/src/main/org/bson/PackedBitBinaryVector.java @@ -0,0 +1,101 @@ +/* + * Copyright 2008-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. + */ + +package org.bson; + +import java.util.Arrays; +import java.util.Objects; + +import static org.bson.assertions.Assertions.assertNotNull; + +/** + * Represents a packed bit vector, where each element of the vector is represented by a single bit (0 or 1). + *

+ * The {@link PackedBitBinaryVector} is used to store data efficiently using the BSON Binary Subtype 9 format. + * + * @mongodb.server.release 6.0 + * @see BinaryVector#packedBitVector(byte[], byte) + * @see BsonBinary#BsonBinary(BinaryVector) + * @see BsonBinary#asVector() + * @since 5.3 + */ +public final class PackedBitBinaryVector extends BinaryVector { + + private final byte padding; + private final byte[] data; + + PackedBitBinaryVector(final byte[] data, final byte padding) { + super(DataType.PACKED_BIT); + this.data = assertNotNull(data); + this.padding = padding; + } + + /** + * Retrieve the underlying byte array representing this {@link PackedBitBinaryVector} vector, where + * each bit represents an element of the vector (either 0 or 1). + *

+ * Note that the {@linkplain #getPadding() padding value} should be considered when interpreting the final byte of the array, + * as it indicates how many least-significant bits are to be ignored. + * + * @return the underlying byte array representing this {@link PackedBitBinaryVector} vector. + * @see #getPadding() + */ + public byte[] getData() { + return assertNotNull(data); + } + + /** + * Returns the padding value for this vector. + * + *

Padding refers to the number of least-significant bits in the final byte that are ignored when retrieving + * {@linkplain #getData() the vector array}. For instance, if the padding value is 3, this means that the last byte contains + * 3 least-significant unused bits, which should be disregarded during operations.

+ *

+ * + * NOTE: The underlying byte array is not copied; changes to the returned array will be reflected in this instance. + * + * @return the padding value (between 0 and 7). + */ + public byte getPadding() { + return this.padding; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PackedBitBinaryVector that = (PackedBitBinaryVector) o; + return padding == that.padding && Arrays.equals(data, that.data); + } + + @Override + public int hashCode() { + return Objects.hash(padding, Arrays.hashCode(data)); + } + + @Override + public String toString() { + return "PackedBitVector{" + + "padding=" + padding + + ", data=" + Arrays.toString(data) + + ", dataType=" + getDataType() + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/BinaryVectorTest.java b/bson/src/test/unit/org/bson/BinaryVectorTest.java new file mode 100644 index 00000000000..57e8b294019 --- /dev/null +++ b/bson/src/test/unit/org/bson/BinaryVectorTest.java @@ -0,0 +1,179 @@ +/* + * Copyright 2008-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. + */ + +package org.bson; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class BinaryVectorTest { + + @Test + void shouldCreateInt8Vector() { + // given + byte[] data = {1, 2, 3, 4, 5}; + + // when + Int8BinaryVector vector = BinaryVector.int8Vector(data); + + // then + assertNotNull(vector); + assertEquals(BinaryVector.DataType.INT8, vector.getDataType()); + assertArrayEquals(data, vector.getData()); + } + + @Test + void shouldThrowExceptionWhenCreatingInt8VectorWithNullData() { + // given + byte[] data = null; + + // when & Then + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> BinaryVector.int8Vector(data)); + assertEquals("data can not be null", exception.getMessage()); + } + + @Test + void shouldCreateFloat32Vector() { + // given + float[] data = {1.0f, 2.0f, 3.0f}; + + // when + Float32BinaryVector vector = BinaryVector.floatVector(data); + + // then + assertNotNull(vector); + assertEquals(BinaryVector.DataType.FLOAT32, vector.getDataType()); + assertArrayEquals(data, vector.getData()); + } + + @Test + void shouldThrowExceptionWhenCreatingFloat32VectorWithNullData() { + // given + float[] data = null; + + // when & Then + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> BinaryVector.floatVector(data)); + assertEquals("data can not be null", exception.getMessage()); + } + + + @ParameterizedTest(name = "{index}: validPadding={0}") + @ValueSource(bytes = {0, 1, 2, 3, 4, 5, 6, 7}) + void shouldCreatePackedBitVector(final byte validPadding) { + // given + byte[] data = {(byte) 0b10101010, (byte) 0b01010101}; + + // when + PackedBitBinaryVector vector = BinaryVector.packedBitVector(data, validPadding); + + // then + assertNotNull(vector); + assertEquals(BinaryVector.DataType.PACKED_BIT, vector.getDataType()); + assertArrayEquals(data, vector.getData()); + assertEquals(validPadding, vector.getPadding()); + } + + @ParameterizedTest(name = "{index}: invalidPadding={0}") + @ValueSource(bytes = {-1, 8}) + void shouldThrowExceptionWhenPackedBitVectorHasInvalidPadding(final byte invalidPadding) { + // given + byte[] data = {(byte) 0b10101010}; + + // when & Then + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + BinaryVector.packedBitVector(data, invalidPadding)); + assertEquals("state should be: Padding must be between 0 and 7 bits. Provided padding: " + invalidPadding, exception.getMessage()); + } + + @Test + void shouldThrowExceptionWhenPackedBitVectorIsCreatedWithNullData() { + // given + byte[] data = null; + byte padding = 0; + + // when & Then + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + BinaryVector.packedBitVector(data, padding)); + assertEquals("data can not be null", exception.getMessage()); + } + + @Test + void shouldCreatePackedBitVectorWithZeroPaddingAndEmptyData() { + // given + byte[] data = new byte[0]; + byte padding = 0; + + // when + PackedBitBinaryVector vector = BinaryVector.packedBitVector(data, padding); + + // then + assertNotNull(vector); + assertEquals(BinaryVector.DataType.PACKED_BIT, vector.getDataType()); + assertArrayEquals(data, vector.getData()); + assertEquals(padding, vector.getPadding()); + } + + @Test + void shouldThrowExceptionWhenPackedBitVectorWithNonZeroPaddingAndEmptyData() { + // given + byte[] data = new byte[0]; + byte padding = 1; + + // when & Then + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + BinaryVector.packedBitVector(data, padding)); + assertEquals("state should be: Padding must be 0 if vector is empty. Provided padding: " + padding, exception.getMessage()); + } + + @Test + void shouldThrowExceptionWhenRetrievingInt8DataFromNonInt8Vector() { + // given + float[] data = {1.0f, 2.0f}; + BinaryVector vector = BinaryVector.floatVector(data); + + // when & Then + IllegalStateException exception = assertThrows(IllegalStateException.class, vector::asInt8Vector); + assertEquals("Expected vector data type INT8, but found FLOAT32", exception.getMessage()); + } + + @Test + void shouldThrowExceptionWhenRetrievingFloat32DataFromNonFloat32Vector() { + // given + byte[] data = {1, 2, 3}; + BinaryVector vector = BinaryVector.int8Vector(data); + + // when & Then + IllegalStateException exception = assertThrows(IllegalStateException.class, vector::asFloat32Vector); + assertEquals("Expected vector data type FLOAT32, but found INT8", exception.getMessage()); + } + + @Test + void shouldThrowExceptionWhenRetrievingPackedBitDataFromNonPackedBitVector() { + // given + float[] data = {1.0f, 2.0f}; + BinaryVector vector = BinaryVector.floatVector(data); + + // when & Then + IllegalStateException exception = assertThrows(IllegalStateException.class, vector::asPackedBitVector); + assertEquals("Expected vector data type PACKED_BIT, but found FLOAT32", exception.getMessage()); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/BinaryVectorCodecTest.java b/bson/src/test/unit/org/bson/codecs/BinaryVectorCodecTest.java new file mode 100644 index 00000000000..7480e78aab7 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/BinaryVectorCodecTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2008-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. + */ + +package org.bson.codecs; + +import org.bson.BsonBinary; +import org.bson.BsonBinaryReader; +import org.bson.BsonBinarySubType; +import org.bson.BsonBinaryWriter; +import org.bson.BsonDocument; +import org.bson.BsonInvalidOperationException; +import org.bson.BsonType; +import org.bson.BsonWriter; +import org.bson.ByteBufNIO; +import org.bson.Float32BinaryVector; +import org.bson.Int8BinaryVector; +import org.bson.PackedBitBinaryVector; +import org.bson.BinaryVector; +import org.bson.io.BasicOutputBuffer; +import org.bson.io.ByteBufferBsonInput; +import org.bson.io.OutputBuffer; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.stream.Stream; + +import static org.bson.BsonHelper.toBson; +import static org.bson.assertions.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class BinaryVectorCodecTest extends CodecTestCase { + + private static Stream provideVectorsAndCodecs() { + return Stream.of( + arguments(BinaryVector.floatVector(new float[]{1.1f, 2.2f, 3.3f}), new Float32VectorCodec(), Float32BinaryVector.class), + arguments(BinaryVector.int8Vector(new byte[]{10, 20, 30, 40}), new Int8VectorCodec(), Int8BinaryVector.class), + arguments(BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new PackedBitVectorCodec(), PackedBitBinaryVector.class), + arguments(BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new VectorCodec(), BinaryVector.class), + arguments(BinaryVector.int8Vector(new byte[]{10, 20, 30, 40}), new VectorCodec(), BinaryVector.class), + arguments(BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new VectorCodec(), BinaryVector.class) + ); + } + + @ParameterizedTest + @MethodSource("provideVectorsAndCodecs") + void shouldEncodeVector(final BinaryVector vectorToEncode, final Codec vectorCodec) throws IOException { + // given + BsonBinary bsonBinary = new BsonBinary(vectorToEncode); + byte[] encodedVector = bsonBinary.getData(); + ByteArrayOutputStream expectedStream = new ByteArrayOutputStream(); + // Total length of a Document (int 32). It is 0, because we do not expect + // codec to write the end of the document (that is when we back-patch the length of the document). + expectedStream.write(new byte[]{0, 0, 0, 0}); + // Bson type + expectedStream.write((byte) BsonType.BINARY.getValue()); + // Field name "b4" + expectedStream.write(new byte[]{98, 52, 0}); + // Total length of binary data (little-endian format) + expectedStream.write(new byte[]{(byte) encodedVector.length, 0, 0, 0}); + // Vector binary subtype + expectedStream.write(BsonBinarySubType.VECTOR.getValue()); + // Actual BSON binary data + expectedStream.write(encodedVector); + + OutputBuffer buffer = new BasicOutputBuffer(); + BsonWriter writer = new BsonBinaryWriter(buffer); + writer.writeStartDocument(); + writer.writeName("b4"); + + // when + vectorCodec.encode(writer, vectorToEncode, EncoderContext.builder().build()); + + // then + assertArrayEquals(expectedStream.toByteArray(), buffer.toByteArray()); + } + + @ParameterizedTest + @MethodSource("provideVectorsAndCodecs") + void shouldDecodeVector(final BinaryVector vectorToDecode, final Codec vectorCodec) { + // given + OutputBuffer buffer = new BasicOutputBuffer(); + BsonWriter writer = new BsonBinaryWriter(buffer); + writer.writeStartDocument(); + writer.writeName("vector"); + writer.writeBinaryData(new BsonBinary(vectorToDecode)); + writer.writeEndDocument(); + + BsonBinaryReader reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(buffer.toByteArray())))); + reader.readStartDocument(); + + // when + BinaryVector decodedVector = vectorCodec.decode(reader, DecoderContext.builder().build()); + + // then + assertDoesNotThrow(reader::readEndDocument); + assertNotNull(decodedVector); + assertEquals(vectorToDecode, decodedVector); + } + + + @ParameterizedTest + @EnumSource(value = BsonBinarySubType.class, mode = EnumSource.Mode.EXCLUDE, names = {"VECTOR"}) + void shouldThrowExceptionForInvalidSubType(final BsonBinarySubType subType) { + // given + BsonDocument document = new BsonDocument("name", new BsonBinary(subType.getValue(), new byte[]{})); + BsonBinaryReader reader = new BsonBinaryReader(toBson(document)); + reader.readStartDocument(); + + // when & then + Stream.of(new Float32VectorCodec(), new Int8VectorCodec(), new PackedBitVectorCodec()) + .forEach(codec -> { + BsonInvalidOperationException exception = assertThrows(BsonInvalidOperationException.class, () -> + codec.decode(reader, DecoderContext.builder().build())); + assertEquals("Expected vector binary subtype 9 but found: " + subType.getValue(), exception.getMessage()); + }); + } + + + @ParameterizedTest + @MethodSource("provideVectorsAndCodecs") + void shouldReturnCorrectEncoderClass(final BinaryVector vector, + final Codec codec, + final Class expectedEncoderClass) { + // when + Class encoderClass = codec.getEncoderClass(); + + // then + assertEquals(expectedEncoderClass, encoderClass); + } +} diff --git a/bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java b/bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java new file mode 100644 index 00000000000..b811a32f70c --- /dev/null +++ b/bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java @@ -0,0 +1,276 @@ +/* + * Copyright 2008-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. + */ + +package org.bson.vector; + +import org.bson.BsonArray; +import org.bson.BsonBinary; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.bson.Float32BinaryVector; +import org.bson.PackedBitBinaryVector; +import org.bson.BinaryVector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import util.JsonPoweredTestHelper; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static java.lang.String.format; +import static org.bson.BsonHelper.decodeToDocument; +import static org.bson.BsonHelper.encodeToHex; +import static org.bson.internal.vector.VectorHelper.determineVectorDType; +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +/** + * See + * JSON-based tests that included in test resources. + */ +class BinaryVectorGenericBsonTest { + + private static final List TEST_NAMES_TO_IGNORE = Arrays.asList( + //NO API to set padding for floats available. + "FLOAT32 with padding", + //NO API to set padding for floats available. + "INT8 with padding", + //It is impossible to provide float inputs for INT8 in the API. + "INT8 with float inputs", + //It is impossible to provide float inputs for INT8. + "Underflow Vector PACKED_BIT", + //It is impossible to provide float inputs for PACKED_BIT in the API. + "Vector with float values PACKED_BIT", + //It is impossible to provide float inputs for INT8. + "Overflow Vector PACKED_BIT", + //It is impossible to overflow byte with values higher than 127 in the API. + "Overflow Vector INT8", + //It is impossible to underflow byte with values lower than -128 in the API. + "Underflow Vector INT8"); + + + @ParameterizedTest(name = "{0}") + @MethodSource("provideTestCases") + void shouldPassAllOutcomes(@SuppressWarnings("unused") final String description, + final BsonDocument testDefinition, final BsonDocument testCase) { + assumeFalse(TEST_NAMES_TO_IGNORE.contains(testCase.get("description").asString().getValue())); + + String testKey = testDefinition.getString("test_key").getValue(); + boolean isValidVector = testCase.getBoolean("valid").getValue(); + if (isValidVector) { + runValidTestCase(testKey, testCase); + } else { + runInvalidTestCase(testCase); + } + } + + private static void runInvalidTestCase(final BsonDocument testCase) { + BsonArray arrayVector = testCase.getArray("vector"); + byte expectedPadding = (byte) testCase.getInt32("padding").getValue(); + byte dtypeByte = Byte.decode(testCase.getString("dtype_hex").getValue()); + BinaryVector.DataType expectedDType = determineVectorDType(dtypeByte); + + switch (expectedDType) { + case INT8: + byte[] expectedVectorData = toByteArray(arrayVector); + assertValidationException(assertThrows(RuntimeException.class, + () -> BinaryVector.int8Vector(expectedVectorData))); + break; + case PACKED_BIT: + byte[] expectedVectorPackedBitData = toByteArray(arrayVector); + assertValidationException(assertThrows(RuntimeException.class, + () -> BinaryVector.packedBitVector(expectedVectorPackedBitData, expectedPadding))); + break; + case FLOAT32: + float[] expectedFloatVector = toFloatArray(arrayVector); + assertValidationException(assertThrows(RuntimeException.class, () -> BinaryVector.floatVector(expectedFloatVector))); + break; + default: + throw new IllegalArgumentException("Unsupported vector data type: " + expectedDType); + } + } + + private static void runValidTestCase(final String testKey, final BsonDocument testCase) { + String description = testCase.getString("description").getValue(); + byte dtypeByte = Byte.decode(testCase.getString("dtype_hex").getValue()); + + byte expectedPadding = (byte) testCase.getInt32("padding").getValue(); + BinaryVector.DataType expectedDType = determineVectorDType(dtypeByte); + String expectedCanonicalBsonHex = testCase.getString("canonical_bson").getValue().toUpperCase(); + + BsonArray arrayVector = testCase.getArray("vector"); + BsonDocument actualDecodedDocument = decodeToDocument(expectedCanonicalBsonHex, description); + BinaryVector actualVector = actualDecodedDocument.getBinary("vector").asVector(); + + switch (expectedDType) { + case INT8: + byte[] expectedVectorData = toByteArray(arrayVector); + byte[] actualVectorData = actualVector.asInt8Vector().getData(); + assertVectorDecoding( + expectedVectorData, + expectedDType, + actualVectorData, + actualVector); + + assertThatVectorCreationResultsInCorrectBinary(BinaryVector.int8Vector(expectedVectorData), + testKey, + actualDecodedDocument, + expectedCanonicalBsonHex, + description); + break; + case PACKED_BIT: + PackedBitBinaryVector actualPackedBitVector = actualVector.asPackedBitVector(); + byte[] expectedVectorPackedBitData = toByteArray(arrayVector); + assertVectorDecoding( + expectedVectorPackedBitData, + expectedDType, expectedPadding, + actualPackedBitVector); + + assertThatVectorCreationResultsInCorrectBinary( + BinaryVector.packedBitVector(expectedVectorPackedBitData, expectedPadding), + testKey, + actualDecodedDocument, + expectedCanonicalBsonHex, + description); + break; + case FLOAT32: + Float32BinaryVector actualFloat32Vector = actualVector.asFloat32Vector(); + float[] expectedFloatVector = toFloatArray(arrayVector); + assertVectorDecoding( + expectedFloatVector, + expectedDType, + actualFloat32Vector); + assertThatVectorCreationResultsInCorrectBinary( + BinaryVector.floatVector(expectedFloatVector), + testKey, + actualDecodedDocument, + expectedCanonicalBsonHex, + description); + break; + default: + throw new IllegalArgumentException("Unsupported vector data type: " + expectedDType); + } + } + + private static void assertValidationException(final RuntimeException runtimeException) { + assertTrue(runtimeException instanceof IllegalArgumentException || runtimeException instanceof IllegalStateException); + } + + private static void assertThatVectorCreationResultsInCorrectBinary(final BinaryVector expectedVectorData, + final String testKey, + final BsonDocument actualDecodedDocument, + final String expectedCanonicalBsonHex, + final String description) { + BsonDocument documentToEncode = new BsonDocument(testKey, new BsonBinary(expectedVectorData)); + assertEquals(documentToEncode, actualDecodedDocument); + assertEquals(expectedCanonicalBsonHex, encodeToHex(documentToEncode), + format("Failed to create expected BSON for document with description '%s'", description)); + } + + private static void assertVectorDecoding(final byte[] expectedVectorData, + final BinaryVector.DataType expectedDType, + final byte[] actualVectorData, + final BinaryVector actualVector) { + Assertions.assertArrayEquals(actualVectorData, expectedVectorData, + () -> "Actual: " + Arrays.toString(actualVectorData) + " != Expected:" + Arrays.toString(expectedVectorData)); + assertEquals(expectedDType, actualVector.getDataType()); + } + + private static void assertVectorDecoding(final byte[] expectedVectorData, + final BinaryVector.DataType expectedDType, + final byte expectedPadding, + final PackedBitBinaryVector actualVector) { + byte[] actualVectorData = actualVector.getData(); + assertVectorDecoding( + expectedVectorData, + expectedDType, + actualVectorData, + actualVector); + assertEquals(expectedPadding, actualVector.getPadding()); + } + + private static void assertVectorDecoding(final float[] expectedVectorData, + final BinaryVector.DataType expectedDType, + final Float32BinaryVector actualVector) { + float[] actualVectorArray = actualVector.getData(); + Assertions.assertArrayEquals(actualVectorArray, expectedVectorData, + () -> "Actual: " + Arrays.toString(actualVectorArray) + " != Expected:" + Arrays.toString(expectedVectorData)); + assertEquals(expectedDType, actualVector.getDataType()); + } + + private static byte[] toByteArray(final BsonArray arrayVector) { + byte[] bytes = new byte[arrayVector.size()]; + for (int i = 0; i < arrayVector.size(); i++) { + bytes[i] = (byte) arrayVector.get(i).asInt32().getValue(); + } + return bytes; + } + + private static float[] toFloatArray(final BsonArray arrayVector) { + float[] floats = new float[arrayVector.size()]; + for (int i = 0; i < arrayVector.size(); i++) { + BsonValue bsonValue = arrayVector.get(i); + if (bsonValue.isString()) { + floats[i] = parseFloat(bsonValue.asString()); + } else { + floats[i] = (float) arrayVector.get(i).asDouble().getValue(); + } + } + return floats; + } + + private static float parseFloat(final BsonString bsonValue) { + String floatValue = bsonValue.getValue(); + switch (floatValue) { + case "-inf": + return Float.NEGATIVE_INFINITY; + case "inf": + return Float.POSITIVE_INFINITY; + default: + return Float.parseFloat(floatValue); + } + } + + private static Stream provideTestCases() throws URISyntaxException, IOException { + List data = new ArrayList<>(); + for (File file : JsonPoweredTestHelper.getTestFiles("/bson-binary-vector")) { + BsonDocument testDocument = JsonPoweredTestHelper.getTestDocument(file); + for (BsonValue curValue : testDocument.getArray("tests", new BsonArray())) { + BsonDocument testCaseDocument = curValue.asDocument(); + data.add(Arguments.of(createTestCaseDescription(testDocument, testCaseDocument), testDocument, testCaseDocument)); + } + } + return data.stream(); + } + + private static String createTestCaseDescription(final BsonDocument testDocument, + final BsonDocument testCaseDocument) { + boolean isValidTestCase = testCaseDocument.getBoolean("valid").getValue(); + String fileDescription = testDocument.getString("description").getValue(); + String testDescription = testCaseDocument.getString("description").getValue(); + return "[Valid input: " + isValidTestCase + "] " + fileDescription + ": " + testDescription; + } +} diff --git a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesBinaryVectorSearchIntegrationTest.java b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesBinaryVectorSearchIntegrationTest.java new file mode 100644 index 00000000000..0d5aad1085a --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesBinaryVectorSearchIntegrationTest.java @@ -0,0 +1,353 @@ +/* + * Copyright 2008-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. + */ + +package com.mongodb.client.model.search; + +import com.mongodb.MongoInterruptedException; +import com.mongodb.MongoNamespace; +import com.mongodb.client.model.Aggregates; +import com.mongodb.client.model.SearchIndexType; +import com.mongodb.client.test.CollectionHelper; +import com.mongodb.internal.operation.SearchIndexRequest; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.BinaryVector; +import org.bson.codecs.DocumentCodec; +import org.bson.conversions.Bson; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static com.mongodb.ClusterFixture.isAtlasSearchTest; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.client.model.Filters.and; +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.gt; +import static com.mongodb.client.model.Filters.gte; +import static com.mongodb.client.model.Filters.in; +import static com.mongodb.client.model.Filters.lt; +import static com.mongodb.client.model.Filters.lte; +import static com.mongodb.client.model.Filters.ne; +import static com.mongodb.client.model.Filters.nin; +import static com.mongodb.client.model.Filters.or; +import static com.mongodb.client.model.Projections.fields; +import static com.mongodb.client.model.Projections.metaVectorSearchScore; +import static com.mongodb.client.model.search.SearchPath.fieldPath; +import static com.mongodb.client.model.search.VectorSearchOptions.approximateVectorSearchOptions; +import static com.mongodb.client.model.search.VectorSearchOptions.exactVectorSearchOptions; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class AggregatesBinaryVectorSearchIntegrationTest { + private static final String EXCEED_WAIT_ATTEMPTS_ERROR_MESSAGE = + "Exceeded maximum attempts waiting for Search Index creation in Atlas cluster. Index document: %s"; + + private static final String VECTOR_INDEX = "vector_search_index"; + private static final String VECTOR_FIELD_INT_8 = "int8Vector"; + private static final String VECTOR_FIELD_FLOAT_32 = "float32Vector"; + private static final String VECTOR_FIELD_LEGACY_DOUBLE_LIST = "legacyDoubleVector"; + private static final int LIMIT = 5; + private static final String FIELD_YEAR = "year"; + private static CollectionHelper collectionHelper; + private static final BsonDocument VECTOR_SEARCH_INDEX_DEFINITION = BsonDocument.parse( + "{" + + " fields: [" + + " {" + + " path: '" + VECTOR_FIELD_INT_8 + "'," + + " numDimensions: 5," + + " similarity: 'cosine'," + + " type: 'vector'," + + " }," + + " {" + + " path: '" + VECTOR_FIELD_FLOAT_32 + "'," + + " numDimensions: 5," + + " similarity: 'cosine'," + + " type: 'vector'," + + " }," + + " {" + + " path: '" + VECTOR_FIELD_LEGACY_DOUBLE_LIST + "'," + + " numDimensions: 5," + + " similarity: 'cosine'," + + " type: 'vector'," + + " }," + + " {" + + " path: '" + FIELD_YEAR + "'," + + " type: 'filter'," + + " }," + + " ]" + + "}"); + + @BeforeAll + static void beforeAll() { + assumeTrue(isAtlasSearchTest()); + assumeTrue(serverVersionAtLeast(6, 0)); + + collectionHelper = + new CollectionHelper<>(new DocumentCodec(), new MongoNamespace("javaVectorSearchTest", AggregatesBinaryVectorSearchIntegrationTest.class.getSimpleName())); + collectionHelper.drop(); + collectionHelper.insertDocuments( + new Document() + .append("_id", 0) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{0, 1, 2, 3, 4})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{0.0001, 1.12345, 2.23456, 3.34567, 4.45678}) + .append(FIELD_YEAR, 2016), + new Document() + .append("_id", 1) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{1, 2, 3, 4, 5})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{1.0001f, 2.12345f, 3.23456f, 4.34567f, 5.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{1.0001, 2.12345, 3.23456, 4.34567, 5.45678}) + .append(FIELD_YEAR, 2017), + new Document() + .append("_id", 2) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{2, 3, 4, 5, 6})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{2.0002f, 3.12345f, 4.23456f, 5.34567f, 6.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{2.0002, 3.12345, 4.23456, 5.34567, 6.45678}) + .append(FIELD_YEAR, 2018), + new Document() + .append("_id", 3) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{3, 4, 5, 6, 7})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{3.0003f, 4.12345f, 5.23456f, 6.34567f, 7.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{3.0003, 4.12345, 5.23456, 6.34567, 7.45678}) + .append(FIELD_YEAR, 2019), + new Document() + .append("_id", 4) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{4, 5, 6, 7, 8})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{4.0004f, 5.12345f, 6.23456f, 7.34567f, 8.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{4.0004, 5.12345, 6.23456, 7.34567, 8.45678}) + .append(FIELD_YEAR, 2020), + new Document() + .append("_id", 5) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{5, 6, 7, 8, 9})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{5.0005f, 6.12345f, 7.23456f, 8.34567f, 9.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{5.0005, 6.12345, 7.23456, 8.34567, 9.45678}) + .append(FIELD_YEAR, 2021), + new Document() + .append("_id", 6) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{6, 7, 8, 9, 10})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{6.0006f, 7.12345f, 8.23456f, 9.34567f, 10.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{6.0006, 7.12345, 8.23456, 9.34567, 10.45678}) + .append(FIELD_YEAR, 2022), + new Document() + .append("_id", 7) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{7, 8, 9, 10, 11})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{7.0007f, 8.12345f, 9.23456f, 10.34567f, 11.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{7.0007, 8.12345, 9.23456, 10.34567, 11.45678}) + .append(FIELD_YEAR, 2023), + new Document() + .append("_id", 8) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{8, 9, 10, 11, 12})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{8.0008f, 9.12345f, 10.23456f, 11.34567f, 12.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{8.0008, 9.12345, 10.23456, 11.34567, 12.45678}) + .append(FIELD_YEAR, 2024), + new Document() + .append("_id", 9) + .append(VECTOR_FIELD_INT_8, BinaryVector.int8Vector(new byte[]{9, 10, 11, 12, 13})) + .append(VECTOR_FIELD_FLOAT_32, BinaryVector.floatVector(new float[]{9.0009f, 10.12345f, 11.23456f, 12.34567f, 13.45678f})) + .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{9.0009, 10.12345, 11.23456, 12.34567, 13.45678}) + .append(FIELD_YEAR, 2025) + ); + + collectionHelper.createSearchIndex( + new SearchIndexRequest(VECTOR_SEARCH_INDEX_DEFINITION, VECTOR_INDEX, + SearchIndexType.vectorSearch())); + awaitIndexCreation(); + } + + @AfterAll + static void afterAll() { + if (collectionHelper != null) { + collectionHelper.drop(); + } + } + + private static Stream provideSupportedVectors() { + return Stream.of( + arguments(BinaryVector.int8Vector(new byte[]{0, 1, 2, 3, 4}), + // `multi` is used here only to verify that it is tolerated + fieldPath(VECTOR_FIELD_INT_8).multi("ignored"), + approximateVectorSearchOptions(LIMIT * 2)), + arguments(BinaryVector.int8Vector(new byte[]{0, 1, 2, 3, 4}), + fieldPath(VECTOR_FIELD_INT_8), + approximateVectorSearchOptions(LIMIT * 2)), + + arguments(BinaryVector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), + // `multi` is used here only to verify that it is tolerated + fieldPath(VECTOR_FIELD_FLOAT_32).multi("ignored"), + approximateVectorSearchOptions(LIMIT * 2)), + arguments(BinaryVector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), + fieldPath(VECTOR_FIELD_FLOAT_32), + approximateVectorSearchOptions(LIMIT * 2)), + + arguments(BinaryVector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), + // `multi` is used here only to verify that it is tolerated + fieldPath(VECTOR_FIELD_FLOAT_32).multi("ignored"), + exactVectorSearchOptions()), + arguments(BinaryVector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), + fieldPath(VECTOR_FIELD_FLOAT_32), + exactVectorSearchOptions()), + + arguments(BinaryVector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), + // `multi` is used here only to verify that it is tolerated + fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST).multi("ignored"), + exactVectorSearchOptions()), + arguments(BinaryVector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), + fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST), + exactVectorSearchOptions()), + + arguments(BinaryVector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), + // `multi` is used here only to verify that it is tolerated + fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST).multi("ignored"), + approximateVectorSearchOptions(LIMIT * 2)), + arguments(BinaryVector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}), + fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST), + approximateVectorSearchOptions(LIMIT * 2)) + ); + } + + @ParameterizedTest + @MethodSource("provideSupportedVectors") + void shouldSearchByVectorWithSearchScore(final BinaryVector vector, + final FieldSearchPath fieldSearchPath, + final VectorSearchOptions vectorSearchOptions) { + //given + List pipeline = asList( + Aggregates.vectorSearch( + fieldSearchPath, + vector, + VECTOR_INDEX, LIMIT, + vectorSearchOptions), + Aggregates.project( + fields( + metaVectorSearchScore("vectorSearchScore") + )) + ); + + //when + List aggregate = collectionHelper.aggregate(pipeline); + + //then + Assertions.assertEquals(LIMIT, aggregate.size()); + assertScoreIsDecreasing(aggregate); + Document highestScoreDocument = aggregate.get(0); + assertEquals(1, highestScoreDocument.getDouble("vectorSearchScore")); + } + + @ParameterizedTest + @MethodSource("provideSupportedVectors") + void shouldSearchByVector(final BinaryVector vector, + final FieldSearchPath fieldSearchPath, + final VectorSearchOptions vectorSearchOptions) { + //given + List pipeline = asList( + Aggregates.vectorSearch( + fieldSearchPath, + vector, + VECTOR_INDEX, LIMIT, + vectorSearchOptions) + ); + + //when + List aggregate = collectionHelper.aggregate(pipeline); + + //then + Assertions.assertEquals(LIMIT, aggregate.size()); + assertFalse( + aggregate.stream() + .anyMatch(document -> document.containsKey("vectorSearchScore")) + ); + } + + @ParameterizedTest + @MethodSource("provideSupportedVectors") + void shouldSearchByVectorWithFilter(final BinaryVector vector, + final FieldSearchPath fieldSearchPath, + final VectorSearchOptions vectorSearchOptions) { + Consumer asserter = filter -> { + List pipeline = singletonList( + Aggregates.vectorSearch( + fieldSearchPath, vector, VECTOR_INDEX, 1, + vectorSearchOptions.filter(filter)) + ); + + List aggregate = collectionHelper.aggregate(pipeline); + Assertions.assertFalse(aggregate.isEmpty()); + }; + + assertAll( + () -> asserter.accept(lt("year", 2020)), + () -> asserter.accept(lte("year", 2020)), + () -> asserter.accept(eq("year", 2020)), + () -> asserter.accept(gte("year", 2016)), + () -> asserter.accept(gt("year", 2015)), + () -> asserter.accept(ne("year", 2016)), + () -> asserter.accept(in("year", 2000, 2024)), + () -> asserter.accept(nin("year", 2000, 2024)), + () -> asserter.accept(and(gte("year", 2015), lte("year", 2017))), + () -> asserter.accept(or(eq("year", 2015), eq("year", 2017))) + ); + } + + private static void assertScoreIsDecreasing(final List aggregate) { + double previousScore = Integer.MAX_VALUE; + for (Document document : aggregate) { + Double vectorSearchScore = document.getDouble("vectorSearchScore"); + assertTrue(vectorSearchScore > 0, "Expected positive score"); + assertTrue(vectorSearchScore < previousScore, "Expected decreasing score"); + previousScore = vectorSearchScore; + } + } + + private static void awaitIndexCreation() { + int attempts = 10; + Optional searchIndex = Optional.empty(); + + while (attempts-- > 0) { + searchIndex = collectionHelper.listSearchIndex(VECTOR_INDEX); + if (searchIndex.filter(document -> document.getBoolean("queryable")) + .isPresent()) { + return; + } + + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new MongoInterruptedException(null, e); + } + } + + searchIndex.ifPresent(document -> + Assertions.fail(format(EXCEED_WAIT_ATTEMPTS_ERROR_MESSAGE, document.toJson()))); + Assertions.fail(format(EXCEED_WAIT_ATTEMPTS_ERROR_MESSAGE, "null")); + } +} diff --git a/driver-core/src/test/unit/com/mongodb/client/model/search/BinaryVectorSearchOptionsTest.java b/driver-core/src/test/unit/com/mongodb/client/model/search/BinaryVectorSearchOptionsTest.java new file mode 100644 index 00000000000..1fde037dbef --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/client/model/search/BinaryVectorSearchOptionsTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2008-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. + */ +package com.mongodb.client.model.search; + +import com.mongodb.client.model.Filters; +import org.bson.BsonBoolean; +import org.bson.BsonDocument; +import org.bson.BsonInt64; +import org.bson.BsonString; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +final class BinaryVectorSearchOptionsTest { + @Test + void approximateVectorSearchOptions() { + assertEquals( + new BsonDocument().append("numCandidates", new BsonInt64(1)), + VectorSearchOptions.approximateVectorSearchOptions(1) + .toBsonDocument() + ); + } + + @Test + void exactVectorSearchOptions() { + assertEquals( + new BsonDocument().append("exact", new BsonBoolean(true)), + VectorSearchOptions.exactVectorSearchOptions() + .toBsonDocument() + ); + } + + @Test + void option() { + assertEquals( + VectorSearchOptions.approximateVectorSearchOptions(1) + .filter(Filters.lt("fieldName", 1)) + .toBsonDocument(), + VectorSearchOptions.approximateVectorSearchOptions(1) + .option("filter", Filters.lt("fieldName", 1)) + .toBsonDocument()); + } + + @Test + void filterApproximate() { + assertEquals( + new BsonDocument() + .append("filter", Filters.lt("fieldName", 1).toBsonDocument()) + .append("numCandidates", new BsonInt64(1)), + VectorSearchOptions.approximateVectorSearchOptions(1) + .filter(Filters.lt("fieldName", 1)) + .toBsonDocument() + ); + } + + @Test + void filterExact() { + assertEquals( + new BsonDocument() + .append("filter", Filters.lt("fieldName", 1).toBsonDocument()) + .append("exact", new BsonBoolean(true)), + VectorSearchOptions.exactVectorSearchOptions() + .filter(Filters.lt("fieldName", 1)) + .toBsonDocument() + ); + } + + @Test + void optionsApproximate() { + assertEquals( + new BsonDocument() + .append("name", new BsonString("value")) + .append("filter", Filters.lt("fieldName", 1).toBsonDocument()) + .append("numCandidates", new BsonInt64(1)), + VectorSearchOptions.approximateVectorSearchOptions(1) + .option("name", "value") + .filter(Filters.lt("fieldName", 0)) + .option("filter", Filters.lt("fieldName", 1)) + .option("numCandidates", new BsonInt64(1)) + .toBsonDocument() + ); + } + + @Test + void optionsExact() { + assertEquals( + new BsonDocument() + .append("name", new BsonString("value")) + .append("filter", Filters.lt("fieldName", 1).toBsonDocument()) + .append("exact", new BsonBoolean(true)), + VectorSearchOptions.exactVectorSearchOptions() + .option("name", "value") + .filter(Filters.lt("fieldName", 0)) + .option("filter", Filters.lt("fieldName", 1)) + .option("exact", new BsonBoolean(true)) + .toBsonDocument() + ); + } + + @Test + void approximateVectorSearchOptionsIsUnmodifiable() { + String expected = VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().toJson(); + VectorSearchOptions.approximateVectorSearchOptions(1).option("name", "value"); + assertEquals(expected, VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().toJson()); + } + + @Test + void approximateVectorSearchOptionsIsImmutable() { + String expected = VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().toJson(); + VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().append("name", new BsonString("value")); + assertEquals(expected, VectorSearchOptions.approximateVectorSearchOptions(1).toBsonDocument().toJson()); + } +} diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/BinaryVectorFunctionalTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/BinaryVectorFunctionalTest.java new file mode 100644 index 00000000000..ef8d8d1d817 --- /dev/null +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/BinaryVectorFunctionalTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2008-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. + */ + +package com.mongodb.reactivestreams.client.vector; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.vector.AbstractBinaryVectorFunctionalTest; +import com.mongodb.reactivestreams.client.MongoClients; +import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; + +public class BinaryVectorFunctionalTest extends AbstractBinaryVectorFunctionalTest { + @Override + protected MongoClient getMongoClient(final MongoClientSettings settings) { + return new SyncMongoClient(MongoClients.create(settings)); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractBinaryVectorFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractBinaryVectorFunctionalTest.java new file mode 100644 index 00000000000..56a37e536f9 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractBinaryVectorFunctionalTest.java @@ -0,0 +1,346 @@ +/* + * Copyright 2008-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. + */ + +package com.mongodb.client.vector; + +import com.mongodb.MongoClientSettings; +import com.mongodb.ReadConcern; +import com.mongodb.ReadPreference; +import com.mongodb.WriteConcern; +import com.mongodb.client.Fixture; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.OperationTest; +import org.bson.BsonBinary; +import org.bson.BsonBinarySubType; +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.bson.Float32BinaryVector; +import org.bson.Int8BinaryVector; +import org.bson.PackedBitBinaryVector; +import org.bson.BinaryVector; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.PojoCodecProvider; +import org.bson.types.Binary; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry; +import static org.bson.BinaryVector.DataType.FLOAT32; +import static org.bson.BinaryVector.DataType.INT8; +import static org.bson.BinaryVector.DataType.PACKED_BIT; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; +import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public abstract class AbstractBinaryVectorFunctionalTest extends OperationTest { + + private static final byte VECTOR_SUBTYPE = BsonBinarySubType.VECTOR.getValue(); + private static final String FIELD_VECTOR = "vector"; + private static final CodecRegistry CODEC_REGISTRY = fromRegistries(getDefaultCodecRegistry(), + fromProviders(PojoCodecProvider + .builder() + .automatic(true).build())); + private MongoCollection documentCollection; + + private MongoClient mongoClient; + + @BeforeEach + public void setUp() { + super.beforeEach(); + mongoClient = getMongoClient(getMongoClientSettingsBuilder() + .codecRegistry(CODEC_REGISTRY) + .build()); + documentCollection = mongoClient + .getDatabase(getDatabaseName()) + .getCollection(getCollectionName()); + } + + @AfterEach + @SuppressWarnings("try") + public void afterEach() { + try (MongoClient ignore = mongoClient) { + super.afterEach(); + } + } + + private static MongoClientSettings.Builder getMongoClientSettingsBuilder() { + return Fixture.getMongoClientSettingsBuilder() + .readConcern(ReadConcern.MAJORITY) + .writeConcern(WriteConcern.MAJORITY) + .readPreference(ReadPreference.primary()); + } + + protected abstract MongoClient getMongoClient(MongoClientSettings settings); + + @ParameterizedTest + @ValueSource(bytes = {-1, 1, 2, 3, 4, 5, 6, 7, 8}) + void shouldThrowExceptionForInvalidPackedBitArrayPaddingWhenDecodeEmptyVector(final byte invalidPadding) { + //given + Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{PACKED_BIT.getValue(), invalidPadding}); + documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector)); + + // when & then + BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> { + findExactlyOne(documentCollection) + .get(FIELD_VECTOR, BinaryVector.class); + }); + assertEquals("Padding must be 0 if vector is empty, but found: " + invalidPadding, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(bytes = {-1, 1}) + void shouldThrowExceptionForInvalidFloat32Padding(final byte invalidPadding) { + // given + Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{FLOAT32.getValue(), invalidPadding, 10, 20, 30, 40}); + documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector)); + + // when & then + BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> { + findExactlyOne(documentCollection) + .get(FIELD_VECTOR, BinaryVector.class); + }); + assertEquals("Padding must be 0 for FLOAT32 data type, but found: " + invalidPadding, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(bytes = {-1, 1}) + void shouldThrowExceptionForInvalidInt8Padding(final byte invalidPadding) { + // given + Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{INT8.getValue(), invalidPadding, 10, 20, 30, 40}); + documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector)); + + // when & then + BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> { + findExactlyOne(documentCollection) + .get(FIELD_VECTOR, BinaryVector.class); + }); + assertEquals("Padding must be 0 for INT8 data type, but found: " + invalidPadding, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(bytes = {-1, 8}) + void shouldThrowExceptionForInvalidPackedBitPadding(final byte invalidPadding) { + // given + Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{PACKED_BIT.getValue(), invalidPadding, 10, 20, 30, 40}); + documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector)); + + // when & then + BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> { + findExactlyOne(documentCollection) + .get(FIELD_VECTOR, BinaryVector.class); + }); + assertEquals("Padding must be between 0 and 7 bits, but found: " + invalidPadding, exception.getMessage()); + } + + private static Stream provideValidVectors() { + return Stream.of( + BinaryVector.floatVector(new float[]{1.1f, 2.2f, 3.3f}), + BinaryVector.int8Vector(new byte[]{10, 20, 30, 40}), + BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3) + ); + } + + @ParameterizedTest + @MethodSource("provideValidVectors") + void shouldStoreAndRetrieveValidVector(final BinaryVector expectedVector) { + // Given + Document documentToInsert = new Document(FIELD_VECTOR, expectedVector) + .append("otherField", 1); // to test that the next field is not affected + documentCollection.insertOne(documentToInsert); + + // when & then + BinaryVector actualVector = findExactlyOne(documentCollection) + .get(FIELD_VECTOR, BinaryVector.class); + + assertEquals(expectedVector, actualVector); + } + + @ParameterizedTest + @MethodSource("provideValidVectors") + void shouldStoreAndRetrieveValidVectorWithBsonBinary(final BinaryVector expectedVector) { + // Given + Document documentToInsert = new Document(FIELD_VECTOR, new BsonBinary(expectedVector)); + documentCollection.insertOne(documentToInsert); + + // when & then + BinaryVector actualVector = findExactlyOne(documentCollection) + .get(FIELD_VECTOR, BinaryVector.class); + + assertEquals(actualVector, actualVector); + } + + @Test + void shouldStoreAndRetrieveValidVectorWithFloatVectorPojo() { + // given + MongoCollection floatVectorPojoMongoCollection = mongoClient + .getDatabase(getDatabaseName()) + .getCollection(getCollectionName()).withDocumentClass(FloatVectorPojo.class); + Float32BinaryVector vector = BinaryVector.floatVector(new float[]{1.1f, 2.2f, 3.3f}); + + // whe + floatVectorPojoMongoCollection.insertOne(new FloatVectorPojo(vector)); + FloatVectorPojo floatVectorPojo = floatVectorPojoMongoCollection.find().first(); + + // then + Assertions.assertNotNull(floatVectorPojo); + assertEquals(vector, floatVectorPojo.getVector()); + } + + @Test + void shouldStoreAndRetrieveValidVectorWithInt8VectorPojo() { + // given + MongoCollection floatVectorPojoMongoCollection = mongoClient + .getDatabase(getDatabaseName()) + .getCollection(getCollectionName()).withDocumentClass(Int8VectorPojo.class); + Int8BinaryVector vector = BinaryVector.int8Vector(new byte[]{10, 20, 30, 40}); + + // when + floatVectorPojoMongoCollection.insertOne(new Int8VectorPojo(vector)); + Int8VectorPojo int8VectorPojo = floatVectorPojoMongoCollection.find().first(); + + // then + Assertions.assertNotNull(int8VectorPojo); + assertEquals(vector, int8VectorPojo.getVector()); + } + + @Test + void shouldStoreAndRetrieveValidVectorWithPackedBitVectorPojo() { + // given + MongoCollection floatVectorPojoMongoCollection = mongoClient + .getDatabase(getDatabaseName()) + .getCollection(getCollectionName()).withDocumentClass(PackedBitVectorPojo.class); + + PackedBitBinaryVector vector = BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3); + + // when + floatVectorPojoMongoCollection.insertOne(new PackedBitVectorPojo(vector)); + PackedBitVectorPojo packedBitVectorPojo = floatVectorPojoMongoCollection.find().first(); + + // then + Assertions.assertNotNull(packedBitVectorPojo); + assertEquals(vector, packedBitVectorPojo.getVector()); + } + + @ParameterizedTest + @MethodSource("provideValidVectors") + void shouldStoreAndRetrieveValidVectorWithGenericVectorPojo(final BinaryVector actualVector) { + // given + MongoCollection floatVectorPojoMongoCollection = mongoClient + .getDatabase(getDatabaseName()) + .getCollection(getCollectionName()).withDocumentClass(VectorPojo.class); + + // when + floatVectorPojoMongoCollection.insertOne(new VectorPojo(actualVector)); + VectorPojo vectorPojo = floatVectorPojoMongoCollection.find().first(); + + //then + Assertions.assertNotNull(vectorPojo); + assertEquals(actualVector, vectorPojo.getVector()); + } + + private Document findExactlyOne(final MongoCollection collection) { + List documents = new ArrayList<>(); + collection.find().into(documents); + assertEquals(1, documents.size(), "Expected exactly one document, but found: " + documents.size()); + return documents.get(0); + } + + public static class VectorPojo { + private BinaryVector vector; + + public VectorPojo() { + } + + public VectorPojo(final BinaryVector vector) { + this.vector = vector; + } + + public BinaryVector getVector() { + return vector; + } + + public void setVector(final BinaryVector vector) { + this.vector = vector; + } + } + + public static class Int8VectorPojo { + private Int8BinaryVector vector; + + public Int8VectorPojo() { + } + + public Int8VectorPojo(final Int8BinaryVector vector) { + this.vector = vector; + } + + public BinaryVector getVector() { + return vector; + } + + public void setVector(final Int8BinaryVector vector) { + this.vector = vector; + } + } + + public static class PackedBitVectorPojo { + private PackedBitBinaryVector vector; + + public PackedBitVectorPojo() { + } + + public PackedBitVectorPojo(final PackedBitBinaryVector vector) { + this.vector = vector; + } + + public BinaryVector getVector() { + return vector; + } + + public void setVector(final PackedBitBinaryVector vector) { + this.vector = vector; + } + } + + public static class FloatVectorPojo { + private Float32BinaryVector vector; + + public FloatVectorPojo() { + } + + public FloatVectorPojo(final Float32BinaryVector vector) { + this.vector = vector; + } + + public BinaryVector getVector() { + return vector; + } + + public void setVector(final Float32BinaryVector vector) { + this.vector = vector; + } + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/vector/BinaryVectorFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/vector/BinaryVectorFunctionalTest.java new file mode 100644 index 00000000000..05bf084dc84 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/vector/BinaryVectorFunctionalTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008-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. + */ + +package com.mongodb.client.vector; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; + +public class BinaryVectorFunctionalTest extends AbstractBinaryVectorFunctionalTest { + @Override + protected MongoClient getMongoClient(final MongoClientSettings settings) { + return MongoClients.create(settings); + } +} From c6ff8ba696ec8dddb887714d53dd42be79e2c468 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 7 Jan 2025 15:22:16 -0800 Subject: [PATCH 3/4] Update Javadoc. JAVA-5710 --- bson/src/main/org/bson/BinaryVector.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/bson/src/main/org/bson/BinaryVector.java b/bson/src/main/org/bson/BinaryVector.java index 4cf01c76dab..630f54ea4f5 100644 --- a/bson/src/main/org/bson/BinaryVector.java +++ b/bson/src/main/org/bson/BinaryVector.java @@ -20,12 +20,8 @@ import static org.bson.assertions.Assertions.notNull; /** - * Represents a vector that is stored and retrieved using the BSON Binary Subtype 9 format. - * This class supports multiple vector {@link DataType}'s and provides static methods to create - * vectors. - *

- * Vectors are densely packed arrays of numbers, all the same type, which are stored efficiently - * in BSON using a binary format. + * Binary Vectors are densely packed arrays of numbers, all the same type, which are stored and retrieved efficiently using the BSON Binary + * Subtype 9 format. This class supports multiple vector {@link DataType}'s and provides static methods to create vectors. *

* NOTE: This class should be treated as sealed: it must not be extended or implemented by consumers of the library. * From 5510cf4383377db1bc900459d938f2bd060d72ac Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 7 Jan 2025 16:41:24 -0800 Subject: [PATCH 4/4] Rename remaining classes. JAVA-5710 --- bson/src/main/org/bson/BsonBinary.java | 6 +- ...ectorCodec.java => BinaryVectorCodec.java} | 2 +- ...dec.java => Float32BinaryVectorCodec.java} | 2 +- ...c.java => PackedBitBinaryVectorCodec.java} | 2 +- .../org/bson/codecs/ValueCodecProvider.java | 12 ++-- ...torHelper.java => BinaryVectorHelper.java} | 4 +- ....java => BinaryBinaryVectorCodecTest.java} | 14 ++--- .../ValueCodecProviderSpecification.groovy | 6 +- .../vector/BinaryVectorGenericBsonTest.java | 2 +- .../AbstractBinaryVectorFunctionalTest.java | 56 +++++++++---------- 10 files changed, 53 insertions(+), 53 deletions(-) rename bson/src/main/org/bson/codecs/{VectorCodec.java => BinaryVectorCodec.java} (96%) rename bson/src/main/org/bson/codecs/{Float32VectorCodec.java => Float32BinaryVectorCodec.java} (95%) rename bson/src/main/org/bson/codecs/{PackedBitVectorCodec.java => PackedBitBinaryVectorCodec.java} (95%) rename bson/src/main/org/bson/internal/vector/{VectorHelper.java => BinaryVectorHelper.java} (98%) rename bson/src/test/unit/org/bson/codecs/{BinaryVectorCodecTest.java => BinaryBinaryVectorCodecTest.java} (92%) diff --git a/bson/src/main/org/bson/BsonBinary.java b/bson/src/main/org/bson/BsonBinary.java index 554072116aa..833a1b5ad29 100644 --- a/bson/src/main/org/bson/BsonBinary.java +++ b/bson/src/main/org/bson/BsonBinary.java @@ -18,12 +18,12 @@ import org.bson.assertions.Assertions; import org.bson.internal.UuidHelper; -import org.bson.internal.vector.VectorHelper; +import org.bson.internal.vector.BinaryVectorHelper; import java.util.Arrays; import java.util.UUID; -import static org.bson.internal.vector.VectorHelper.encodeVectorToBinary; +import static org.bson.internal.vector.BinaryVectorHelper.encodeVectorToBinary; /** * A representation of the BSON Binary type. Note that for performance reasons instances of this class are not immutable, @@ -156,7 +156,7 @@ public BinaryVector asVector() { throw new BsonInvalidOperationException("type must be a Vector subtype."); } - return VectorHelper.decodeBinaryToVector(this.data); + return BinaryVectorHelper.decodeBinaryToVector(this.data); } /** diff --git a/bson/src/main/org/bson/codecs/VectorCodec.java b/bson/src/main/org/bson/codecs/BinaryVectorCodec.java similarity index 96% rename from bson/src/main/org/bson/codecs/VectorCodec.java rename to bson/src/main/org/bson/codecs/BinaryVectorCodec.java index 1378ab094ee..4d23557ad49 100644 --- a/bson/src/main/org/bson/codecs/VectorCodec.java +++ b/bson/src/main/org/bson/codecs/BinaryVectorCodec.java @@ -27,7 +27,7 @@ * Encodes and decodes {@link BinaryVector} objects. * */ - final class VectorCodec implements Codec { + final class BinaryVectorCodec implements Codec { @Override public void encode(final BsonWriter writer, final BinaryVector vectorToEncode, final EncoderContext encoderContext) { diff --git a/bson/src/main/org/bson/codecs/Float32VectorCodec.java b/bson/src/main/org/bson/codecs/Float32BinaryVectorCodec.java similarity index 95% rename from bson/src/main/org/bson/codecs/Float32VectorCodec.java rename to bson/src/main/org/bson/codecs/Float32BinaryVectorCodec.java index 4983ff45dcf..99f740a6873 100644 --- a/bson/src/main/org/bson/codecs/Float32VectorCodec.java +++ b/bson/src/main/org/bson/codecs/Float32BinaryVectorCodec.java @@ -27,7 +27,7 @@ * Encodes and decodes {@link Float32BinaryVector} objects. * */ -final class Float32VectorCodec implements Codec { +final class Float32BinaryVectorCodec implements Codec { @Override public void encode(final BsonWriter writer, final Float32BinaryVector vectorToEncode, final EncoderContext encoderContext) { diff --git a/bson/src/main/org/bson/codecs/PackedBitVectorCodec.java b/bson/src/main/org/bson/codecs/PackedBitBinaryVectorCodec.java similarity index 95% rename from bson/src/main/org/bson/codecs/PackedBitVectorCodec.java rename to bson/src/main/org/bson/codecs/PackedBitBinaryVectorCodec.java index 28d76ead729..c8d0410a4c6 100644 --- a/bson/src/main/org/bson/codecs/PackedBitVectorCodec.java +++ b/bson/src/main/org/bson/codecs/PackedBitBinaryVectorCodec.java @@ -27,7 +27,7 @@ * Encodes and decodes {@link PackedBitBinaryVector} objects. * */ -final class PackedBitVectorCodec implements Codec { +final class PackedBitBinaryVectorCodec implements Codec { @Override public void encode(final BsonWriter writer, final PackedBitBinaryVector vectorToEncode, final EncoderContext encoderContext) { diff --git a/bson/src/main/org/bson/codecs/ValueCodecProvider.java b/bson/src/main/org/bson/codecs/ValueCodecProvider.java index 3a921c1b08a..5c21e048529 100644 --- a/bson/src/main/org/bson/codecs/ValueCodecProvider.java +++ b/bson/src/main/org/bson/codecs/ValueCodecProvider.java @@ -42,10 +42,10 @@ *

  • {@link org.bson.codecs.StringCodec}
  • *
  • {@link org.bson.codecs.SymbolCodec}
  • *
  • {@link org.bson.codecs.UuidCodec}
  • - *
  • {@link VectorCodec}
  • - *
  • {@link Float32VectorCodec}
  • + *
  • {@link BinaryVectorCodec}
  • + *
  • {@link Float32BinaryVectorCodec}
  • *
  • {@link Int8VectorCodec}
  • - *
  • {@link PackedBitVectorCodec}
  • + *
  • {@link PackedBitBinaryVectorCodec}
  • *
  • {@link org.bson.codecs.ByteCodec}
  • *
  • {@link org.bson.codecs.ShortCodec}
  • *
  • {@link org.bson.codecs.ByteArrayCodec}
  • @@ -90,10 +90,10 @@ private void addCodecs() { addCodec(new StringCodec()); addCodec(new SymbolCodec()); addCodec(new OverridableUuidRepresentationUuidCodec()); - addCodec(new VectorCodec()); - addCodec(new Float32VectorCodec()); + addCodec(new BinaryVectorCodec()); + addCodec(new Float32BinaryVectorCodec()); addCodec(new Int8VectorCodec()); - addCodec(new PackedBitVectorCodec()); + addCodec(new PackedBitBinaryVectorCodec()); addCodec(new ByteCodec()); addCodec(new PatternCodec()); diff --git a/bson/src/main/org/bson/internal/vector/VectorHelper.java b/bson/src/main/org/bson/internal/vector/BinaryVectorHelper.java similarity index 98% rename from bson/src/main/org/bson/internal/vector/VectorHelper.java rename to bson/src/main/org/bson/internal/vector/BinaryVectorHelper.java index 9f2657bf315..74d50d334fc 100644 --- a/bson/src/main/org/bson/internal/vector/VectorHelper.java +++ b/bson/src/main/org/bson/internal/vector/BinaryVectorHelper.java @@ -39,13 +39,13 @@ * @see BsonBinary#asVector() * @see BsonBinary#BsonBinary(BinaryVector) */ -public final class VectorHelper { +public final class BinaryVectorHelper { private static final ByteOrder STORED_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN; private static final String ERROR_MESSAGE_UNKNOWN_VECTOR_DATA_TYPE = "Unknown vector data type: "; private static final byte ZERO_PADDING = 0; - private VectorHelper() { + private BinaryVectorHelper() { //NOP } diff --git a/bson/src/test/unit/org/bson/codecs/BinaryVectorCodecTest.java b/bson/src/test/unit/org/bson/codecs/BinaryBinaryVectorCodecTest.java similarity index 92% rename from bson/src/test/unit/org/bson/codecs/BinaryVectorCodecTest.java rename to bson/src/test/unit/org/bson/codecs/BinaryBinaryVectorCodecTest.java index 7480e78aab7..fadddb7a635 100644 --- a/bson/src/test/unit/org/bson/codecs/BinaryVectorCodecTest.java +++ b/bson/src/test/unit/org/bson/codecs/BinaryBinaryVectorCodecTest.java @@ -50,16 +50,16 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.params.provider.Arguments.arguments; -class BinaryVectorCodecTest extends CodecTestCase { +class BinaryBinaryVectorCodecTest extends CodecTestCase { private static Stream provideVectorsAndCodecs() { return Stream.of( - arguments(BinaryVector.floatVector(new float[]{1.1f, 2.2f, 3.3f}), new Float32VectorCodec(), Float32BinaryVector.class), + arguments(BinaryVector.floatVector(new float[]{1.1f, 2.2f, 3.3f}), new Float32BinaryVectorCodec(), Float32BinaryVector.class), arguments(BinaryVector.int8Vector(new byte[]{10, 20, 30, 40}), new Int8VectorCodec(), Int8BinaryVector.class), - arguments(BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new PackedBitVectorCodec(), PackedBitBinaryVector.class), - arguments(BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new VectorCodec(), BinaryVector.class), - arguments(BinaryVector.int8Vector(new byte[]{10, 20, 30, 40}), new VectorCodec(), BinaryVector.class), - arguments(BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new VectorCodec(), BinaryVector.class) + arguments(BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new PackedBitBinaryVectorCodec(), PackedBitBinaryVector.class), + arguments(BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new BinaryVectorCodec(), BinaryVector.class), + arguments(BinaryVector.int8Vector(new byte[]{10, 20, 30, 40}), new BinaryVectorCodec(), BinaryVector.class), + arguments(BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new BinaryVectorCodec(), BinaryVector.class) ); } @@ -129,7 +129,7 @@ void shouldThrowExceptionForInvalidSubType(final BsonBinarySubType subType) { reader.readStartDocument(); // when & then - Stream.of(new Float32VectorCodec(), new Int8VectorCodec(), new PackedBitVectorCodec()) + Stream.of(new Float32BinaryVectorCodec(), new Int8VectorCodec(), new PackedBitBinaryVectorCodec()) .forEach(codec -> { BsonInvalidOperationException exception = assertThrows(BsonInvalidOperationException.class, () -> codec.decode(reader, DecoderContext.builder().build())); diff --git a/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy b/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy index 110c634ba5c..1fd738b5c5c 100644 --- a/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy @@ -62,10 +62,10 @@ class ValueCodecProviderSpecification extends Specification { provider.get(Short, registry) instanceof ShortCodec provider.get(byte[], registry) instanceof ByteArrayCodec provider.get(Float, registry) instanceof FloatCodec - provider.get(BinaryVector, registry) instanceof VectorCodec - provider.get(Float32BinaryVector, registry) instanceof Float32VectorCodec + provider.get(BinaryVector, registry) instanceof BinaryVectorCodec + provider.get(Float32BinaryVector, registry) instanceof Float32BinaryVectorCodec provider.get(Int8BinaryVector, registry) instanceof Int8VectorCodec - provider.get(PackedBitBinaryVector, registry) instanceof PackedBitVectorCodec + provider.get(PackedBitBinaryVector, registry) instanceof PackedBitBinaryVectorCodec provider.get(Binary, registry) instanceof BinaryCodec provider.get(MinKey, registry) instanceof MinKeyCodec diff --git a/bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java b/bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java index b811a32f70c..858174d7cd9 100644 --- a/bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java +++ b/bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java @@ -41,7 +41,7 @@ import static java.lang.String.format; import static org.bson.BsonHelper.decodeToDocument; import static org.bson.BsonHelper.encodeToHex; -import static org.bson.internal.vector.VectorHelper.determineVectorDType; +import static org.bson.internal.vector.BinaryVectorHelper.determineVectorDType; import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractBinaryVectorFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractBinaryVectorFunctionalTest.java index 56a37e536f9..5d61051a997 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractBinaryVectorFunctionalTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractBinaryVectorFunctionalTest.java @@ -195,14 +195,14 @@ void shouldStoreAndRetrieveValidVectorWithBsonBinary(final BinaryVector expected @Test void shouldStoreAndRetrieveValidVectorWithFloatVectorPojo() { // given - MongoCollection floatVectorPojoMongoCollection = mongoClient + MongoCollection floatVectorPojoMongoCollection = mongoClient .getDatabase(getDatabaseName()) - .getCollection(getCollectionName()).withDocumentClass(FloatVectorPojo.class); + .getCollection(getCollectionName()).withDocumentClass(Float32BinaryVectorPojo.class); Float32BinaryVector vector = BinaryVector.floatVector(new float[]{1.1f, 2.2f, 3.3f}); // whe - floatVectorPojoMongoCollection.insertOne(new FloatVectorPojo(vector)); - FloatVectorPojo floatVectorPojo = floatVectorPojoMongoCollection.find().first(); + floatVectorPojoMongoCollection.insertOne(new Float32BinaryVectorPojo(vector)); + Float32BinaryVectorPojo floatVectorPojo = floatVectorPojoMongoCollection.find().first(); // then Assertions.assertNotNull(floatVectorPojo); @@ -212,14 +212,14 @@ void shouldStoreAndRetrieveValidVectorWithFloatVectorPojo() { @Test void shouldStoreAndRetrieveValidVectorWithInt8VectorPojo() { // given - MongoCollection floatVectorPojoMongoCollection = mongoClient + MongoCollection floatVectorPojoMongoCollection = mongoClient .getDatabase(getDatabaseName()) - .getCollection(getCollectionName()).withDocumentClass(Int8VectorPojo.class); + .getCollection(getCollectionName()).withDocumentClass(Int8BinaryVectorPojo.class); Int8BinaryVector vector = BinaryVector.int8Vector(new byte[]{10, 20, 30, 40}); // when - floatVectorPojoMongoCollection.insertOne(new Int8VectorPojo(vector)); - Int8VectorPojo int8VectorPojo = floatVectorPojoMongoCollection.find().first(); + floatVectorPojoMongoCollection.insertOne(new Int8BinaryVectorPojo(vector)); + Int8BinaryVectorPojo int8VectorPojo = floatVectorPojoMongoCollection.find().first(); // then Assertions.assertNotNull(int8VectorPojo); @@ -229,15 +229,15 @@ void shouldStoreAndRetrieveValidVectorWithInt8VectorPojo() { @Test void shouldStoreAndRetrieveValidVectorWithPackedBitVectorPojo() { // given - MongoCollection floatVectorPojoMongoCollection = mongoClient + MongoCollection floatVectorPojoMongoCollection = mongoClient .getDatabase(getDatabaseName()) - .getCollection(getCollectionName()).withDocumentClass(PackedBitVectorPojo.class); + .getCollection(getCollectionName()).withDocumentClass(PackedBitBinaryVectorPojo.class); PackedBitBinaryVector vector = BinaryVector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3); // when - floatVectorPojoMongoCollection.insertOne(new PackedBitVectorPojo(vector)); - PackedBitVectorPojo packedBitVectorPojo = floatVectorPojoMongoCollection.find().first(); + floatVectorPojoMongoCollection.insertOne(new PackedBitBinaryVectorPojo(vector)); + PackedBitBinaryVectorPojo packedBitVectorPojo = floatVectorPojoMongoCollection.find().first(); // then Assertions.assertNotNull(packedBitVectorPojo); @@ -248,13 +248,13 @@ void shouldStoreAndRetrieveValidVectorWithPackedBitVectorPojo() { @MethodSource("provideValidVectors") void shouldStoreAndRetrieveValidVectorWithGenericVectorPojo(final BinaryVector actualVector) { // given - MongoCollection floatVectorPojoMongoCollection = mongoClient + MongoCollection floatVectorPojoMongoCollection = mongoClient .getDatabase(getDatabaseName()) - .getCollection(getCollectionName()).withDocumentClass(VectorPojo.class); + .getCollection(getCollectionName()).withDocumentClass(BinaryVectorPojo.class); // when - floatVectorPojoMongoCollection.insertOne(new VectorPojo(actualVector)); - VectorPojo vectorPojo = floatVectorPojoMongoCollection.find().first(); + floatVectorPojoMongoCollection.insertOne(new BinaryVectorPojo(actualVector)); + BinaryVectorPojo vectorPojo = floatVectorPojoMongoCollection.find().first(); //then Assertions.assertNotNull(vectorPojo); @@ -268,13 +268,13 @@ private Document findExactlyOne(final MongoCollection collection) { return documents.get(0); } - public static class VectorPojo { + public static class BinaryVectorPojo { private BinaryVector vector; - public VectorPojo() { + public BinaryVectorPojo() { } - public VectorPojo(final BinaryVector vector) { + public BinaryVectorPojo(final BinaryVector vector) { this.vector = vector; } @@ -287,13 +287,13 @@ public void setVector(final BinaryVector vector) { } } - public static class Int8VectorPojo { + public static class Int8BinaryVectorPojo { private Int8BinaryVector vector; - public Int8VectorPojo() { + public Int8BinaryVectorPojo() { } - public Int8VectorPojo(final Int8BinaryVector vector) { + public Int8BinaryVectorPojo(final Int8BinaryVector vector) { this.vector = vector; } @@ -306,13 +306,13 @@ public void setVector(final Int8BinaryVector vector) { } } - public static class PackedBitVectorPojo { + public static class PackedBitBinaryVectorPojo { private PackedBitBinaryVector vector; - public PackedBitVectorPojo() { + public PackedBitBinaryVectorPojo() { } - public PackedBitVectorPojo(final PackedBitBinaryVector vector) { + public PackedBitBinaryVectorPojo(final PackedBitBinaryVector vector) { this.vector = vector; } @@ -325,13 +325,13 @@ public void setVector(final PackedBitBinaryVector vector) { } } - public static class FloatVectorPojo { + public static class Float32BinaryVectorPojo { private Float32BinaryVector vector; - public FloatVectorPojo() { + public Float32BinaryVectorPojo() { } - public FloatVectorPojo(final Float32BinaryVector vector) { + public Float32BinaryVectorPojo(final Float32BinaryVector vector) { this.vector = vector; }