From bcc22ba0afe2f383cda26f2a45b216edc8c3c156 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Fri, 18 Oct 2024 09:59:01 -0400 Subject: [PATCH 01/60] Allow valid SRV hostnames with less than 3 parts (#1534) --- .../crud/aggregate-write-readPreference.json | 69 ------------------- .../db-aggregate-write-readPreference.json | 51 -------------- 2 files changed, 120 deletions(-) diff --git a/driver-core/src/test/resources/unified-test-format/crud/aggregate-write-readPreference.json b/driver-core/src/test/resources/unified-test-format/crud/aggregate-write-readPreference.json index bc887e83cbc..c1fa3b4574a 100644 --- a/driver-core/src/test/resources/unified-test-format/crud/aggregate-write-readPreference.json +++ b/driver-core/src/test/resources/unified-test-format/crud/aggregate-write-readPreference.json @@ -78,11 +78,6 @@ "x": 33 } ] - }, - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [] } ], "tests": [ @@ -159,22 +154,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } ] }, { @@ -250,22 +229,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } ] }, { @@ -344,22 +307,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } ] }, { @@ -438,22 +385,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } ] } ] diff --git a/driver-core/src/test/resources/unified-test-format/crud/db-aggregate-write-readPreference.json b/driver-core/src/test/resources/unified-test-format/crud/db-aggregate-write-readPreference.json index 2a81282de81..b6460f001f2 100644 --- a/driver-core/src/test/resources/unified-test-format/crud/db-aggregate-write-readPreference.json +++ b/driver-core/src/test/resources/unified-test-format/crud/db-aggregate-write-readPreference.json @@ -52,13 +52,6 @@ } } ], - "initialData": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [] - } - ], "tests": [ { "description": "Database-level aggregate with $out includes read preference for 5.0+ server", @@ -141,17 +134,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [ - { - "_id": 1 - } - ] - } ] }, { @@ -235,17 +217,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [ - { - "_id": 1 - } - ] - } ] }, { @@ -332,17 +303,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [ - { - "_id": 1 - } - ] - } ] }, { @@ -429,17 +389,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [ - { - "_id": 1 - } - ] - } ] } ] From 96d0c6baf86f3dc3fc3fae31b53efaefb9b87c28 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Wed, 3 Dec 2025 15:06:51 -0800 Subject: [PATCH 02/60] Remove assertion for verbose result (#1835) - Remove an assertion that was failing due to server bug SERVER-113344 where successful operation results are unexpectedly returned in the cursor when verbose results are disabled. - Add unit tests for JAVA-6001 and JAVA-5986. JAVA-6001 JAVA-5986 --------- Co-authored-by: Ross Lawley --- .../connection/DualMessageSequences.java | 4 +- .../operation/ClientBulkWriteOperation.java | 78 +++++-- .../ClientBulkWriteOperationTest.java | 215 ++++++++++++++++++ 3 files changed, 271 insertions(+), 26 deletions(-) create mode 100644 driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java diff --git a/driver-core/src/main/com/mongodb/internal/connection/DualMessageSequences.java b/driver-core/src/main/com/mongodb/internal/connection/DualMessageSequences.java index 0c5a3430c22..0fafa3e6d2a 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DualMessageSequences.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DualMessageSequences.java @@ -16,6 +16,7 @@ package com.mongodb.internal.connection; +import com.mongodb.internal.VisibleForTesting; import org.bson.BsonBinaryWriter; import org.bson.BsonElement; import org.bson.FieldNameValidator; @@ -61,7 +62,8 @@ String getSecondSequenceId() { return secondSequenceId; } - protected abstract EncodeDocumentsResult encodeDocuments(WritersProviderAndLimitsChecker writersProviderAndLimitsChecker); + @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PROTECTED) + public abstract EncodeDocumentsResult encodeDocuments(WritersProviderAndLimitsChecker writersProviderAndLimitsChecker); /** * @see #tryWrite(WriteAction) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index 26ffb6a5732..7dcd632e986 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -772,33 +772,25 @@ ClientBulkWriteResult build(@Nullable final MongoException topLevelError, final deletedCount += response.getNDeleted(); Map insertModelDocumentIds = batchResult.getInsertModelDocumentIds(); for (BsonDocument individualOperationResponse : response.getCursorExhaust()) { + boolean individualOperationSuccessful = individualOperationResponse.getNumber("ok").intValue() == 1; + if (individualOperationSuccessful && !verboseResultsSetting) { + //TODO-JAVA-6002 Previously, assertTrue(verboseResultsSetting) was used when ok == 1 because the server + // was not supposed to return successful operation results in the cursor when verboseResultsSetting is false. + // Due to server bug SERVER-113344, these unexpected results must be ignored until we stop supporting server + // versions affected by this bug. When that happens, restore assertTrue(verboseResultsSetting). + continue; + } int individualOperationIndexInBatch = individualOperationResponse.getInt32("idx").getValue(); int writeModelIndex = batchStartModelIndex + individualOperationIndexInBatch; - if (individualOperationResponse.getNumber("ok").intValue() == 1) { - assertTrue(verboseResultsSetting); - AbstractClientNamespacedWriteModel writeModel = getNamespacedModel(models, writeModelIndex); - if (writeModel instanceof ConcreteClientNamespacedInsertOneModel) { - insertResults.put( - writeModelIndex, - new ConcreteClientInsertOneResult(insertModelDocumentIds.get(individualOperationIndexInBatch))); - } else if (writeModel instanceof ConcreteClientNamespacedUpdateOneModel - || writeModel instanceof ConcreteClientNamespacedUpdateManyModel - || writeModel instanceof ConcreteClientNamespacedReplaceOneModel) { - BsonDocument upsertedIdDocument = individualOperationResponse.getDocument("upserted", null); - updateResults.put( - writeModelIndex, - new ConcreteClientUpdateResult( - individualOperationResponse.getInt32("n").getValue(), - individualOperationResponse.getInt32("nModified", new BsonInt32(0)).getValue(), - upsertedIdDocument == null ? null : upsertedIdDocument.get("_id"))); - } else if (writeModel instanceof ConcreteClientNamespacedDeleteOneModel - || writeModel instanceof ConcreteClientNamespacedDeleteManyModel) { - deleteResults.put( - writeModelIndex, - new ConcreteClientDeleteResult(individualOperationResponse.getInt32("n").getValue())); - } else { - fail(writeModel.getClass().toString()); - } + if (individualOperationSuccessful) { + collectSuccessfulIndividualOperationResult( + individualOperationResponse, + writeModelIndex, + individualOperationIndexInBatch, + insertModelDocumentIds, + insertResults, + updateResults, + deleteResults); } else { batchResultsHaveInfoAboutSuccessfulIndividualOperations = batchResultsHaveInfoAboutSuccessfulIndividualOperations || (orderedSetting && individualOperationIndexInBatch > 0); @@ -838,6 +830,42 @@ ClientBulkWriteResult build(@Nullable final MongoException topLevelError, final } } + private void collectSuccessfulIndividualOperationResult(final BsonDocument individualOperationResponse, + final int writeModelIndex, + final int individualOperationIndexInBatch, + final Map insertModelDocumentIds, + final Map insertResults, + final Map updateResults, + final Map deleteResults) { + AbstractClientNamespacedWriteModel writeModel = getNamespacedModel(models, writeModelIndex); + if (writeModel instanceof ConcreteClientNamespacedInsertOneModel) { + insertResults.put( + writeModelIndex, + new ConcreteClientInsertOneResult(insertModelDocumentIds.get(individualOperationIndexInBatch))); + } else if (writeModel instanceof ConcreteClientNamespacedUpdateOneModel + || writeModel instanceof ConcreteClientNamespacedUpdateManyModel + || writeModel instanceof ConcreteClientNamespacedReplaceOneModel) { + BsonDocument upsertedIdDocument = individualOperationResponse.getDocument("upserted", null); + updateResults.put( + writeModelIndex, + new ConcreteClientUpdateResult( + individualOperationResponse.getInt32("n").getValue(), + //TODO-JAVA-6005 Previously, we did not provide a default value of 0 because the + // server was supposed to return nModified as 0 when no documents were changed. + // Due to server bug SERVER-113026, we must provide a default of 0 until we stop supporting + // server versions affected by this bug. When that happens, remove the default value for nModified. + individualOperationResponse.getInt32("nModified", new BsonInt32(0)).getValue(), + upsertedIdDocument == null ? null : upsertedIdDocument.get("_id"))); + } else if (writeModel instanceof ConcreteClientNamespacedDeleteOneModel + || writeModel instanceof ConcreteClientNamespacedDeleteManyModel) { + deleteResults.put( + writeModelIndex, + new ConcreteClientDeleteResult(individualOperationResponse.getInt32("n").getValue())); + } else { + fail(writeModel.getClass().toString()); + } + } + void onNewServerAddress(final ServerAddress serverAddress) { this.serverAddress = serverAddress; } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java new file mode 100644 index 00000000000..5de1992b69d --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/ClientBulkWriteOperationTest.java @@ -0,0 +1,215 @@ +/* + * 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.internal.operation; + +import com.mongodb.ClusterFixture; +import com.mongodb.MongoNamespace; +import com.mongodb.ServerAddress; +import com.mongodb.WriteConcern; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.bulk.ClientBulkWriteOptions; +import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedReplaceOneModel; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; +import com.mongodb.connection.ClusterId; +import com.mongodb.connection.ConnectionDescription; +import com.mongodb.connection.ServerConnectionState; +import com.mongodb.connection.ServerDescription; +import com.mongodb.connection.ServerId; +import com.mongodb.connection.ServerType; +import com.mongodb.internal.binding.ConnectionSource; +import com.mongodb.internal.binding.ReadWriteBinding; +import com.mongodb.internal.client.model.bulk.AcknowledgedSummaryClientBulkWriteResult; +import com.mongodb.internal.connection.Connection; +import com.mongodb.internal.connection.DualMessageSequences; +import com.mongodb.internal.connection.OperationContext; +import com.mongodb.internal.mockito.MongoMockito; +import org.bson.BsonBinaryWriter; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.io.BasicOutputBuffer; +import org.bson.json.JsonReader; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry; +import static com.mongodb.client.model.bulk.ClientReplaceOneOptions.clientReplaceOneOptions; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; + +class ClientBulkWriteOperationTest { + private static final MongoNamespace NAMESPACE = new MongoNamespace("testDb.testCol"); + private Connection connection; + private ConnectionSource connectionSource; + private ReadWriteBinding binding; + + @BeforeEach + void setUp() { + connection = MongoMockito.mock(Connection.class); + connectionSource = MongoMockito.mock(ConnectionSource.class); + binding = MongoMockito.mock(ReadWriteBinding.class); + + doReturn(new ConnectionDescription(new ServerId(new ClusterId("test"), new ServerAddress()))).when(connection).getDescription(); + doReturn(connection).when(connectionSource).getConnection(any(OperationContext.class)); + doReturn(0).when(connectionSource).release(); + doReturn(0).when(connection).release(); + + doReturn(ServerDescription.builder().address(new ServerAddress()) + .state(ServerConnectionState.CONNECTED) + .type(ServerType.STANDALONE) + .build()).when(connectionSource).getServerDescription(); + doReturn(connectionSource).when(binding).getWriteConnectionSource(any(OperationContext.class)); + } + + + /** + * This test exists due to SERVER-113344 bug. + */ + //TODO-JAVA-6002 + @Test + void shouldIgnoreSuccessfulCursorResultWhenVerboseResultIsFalse() { + //given + mockCommandExecutionResult( + "{'cursor': {" + + " 'id': NumberLong(0)," + + " 'firstBatch': [ { 'ok': 1, 'idx': 0, 'n': 1, 'upserted': { '_id': 1 } } ]," + + " 'ns': 'admin.$cmd.bulkWrite'" + + "}," + + " 'nErrors': 0," + + " 'nInserted': 0," + + " 'nMatched': 0," + + " 'nModified': 0," + + " 'nUpserted': 1," + + " 'nDeleted': 0," + + " 'ok': 1" + + "}" + ); + ClientBulkWriteOptions options = ClientBulkWriteOptions.clientBulkWriteOptions() + .ordered(false).verboseResults(false); + List clientNamespacedReplaceOneModels = singletonList(ClientNamespacedWriteModel.replaceOne( + NAMESPACE, + Filters.empty(), + new Document(), + clientReplaceOneOptions().upsert(true) + )); + ClientBulkWriteOperation op = new ClientBulkWriteOperation( + clientNamespacedReplaceOneModels, + options, + WriteConcern.ACKNOWLEDGED, + false, + getDefaultCodecRegistry()); + //when + ClientBulkWriteResult result = op.execute(binding, ClusterFixture.OPERATION_CONTEXT); + + //then + assertEquals( + new AcknowledgedSummaryClientBulkWriteResult(0, 1, 0, 0, 0), + result); + } + + /** + * This test exists due to SERVER-113026 bug. + */ + //TODO-JAVA-6005 + @Test + void shouldUseDefaultNumberOfModifiedDocumentsWhenMissingInCursor() { + //given + mockCommandExecutionResult("{" + + " cursor: {" + + " id: NumberLong(0)," + + " firstBatch: [ {" + + " 'ok': 1.0," + + " 'idx': 0," + + " 'n': 1," + //nModified field is missing here + + " 'upserted': {" + + " '_id': 1" + + " }" + + " }]," + + " ns: 'admin.$cmd.bulkWrite'" + + " }," + + " nErrors: 0," + + " nInserted: 1," + + " nMatched: 0," + + " nModified: 0," + + " nUpserted: 1," + + " nDeleted: 0," + + " ok: 1" + + "}"); + ClientBulkWriteOptions options = ClientBulkWriteOptions.clientBulkWriteOptions() + .ordered(false).verboseResults(true); + List clientNamespacedReplaceOneModels = singletonList(ClientNamespacedWriteModel.replaceOne( + NAMESPACE, + Filters.empty(), + new Document(), + clientReplaceOneOptions().upsert(true) + )); + ClientBulkWriteOperation op = new ClientBulkWriteOperation( + clientNamespacedReplaceOneModels, + options, + WriteConcern.ACKNOWLEDGED, + false, + getDefaultCodecRegistry()); + //when + ClientBulkWriteResult result = op.execute(binding, ClusterFixture.OPERATION_CONTEXT); + + //then + assertEquals(1, result.getInsertedCount()); + assertEquals(1, result.getUpsertedCount()); + assertEquals(0, result.getMatchedCount()); + assertEquals(0, result.getModifiedCount()); + assertEquals(0, result.getDeletedCount()); + assertTrue(result.getVerboseResults().isPresent()); + } + + private void mockCommandExecutionResult(final String serverResponse) { + doAnswer(invocationOnMock -> { + DualMessageSequences dualMessageSequences = invocationOnMock.getArgument(7); + dualMessageSequences.encodeDocuments(write -> { + write.doAndGetBatchCount(new BsonBinaryWriter(new BasicOutputBuffer()), new BsonBinaryWriter(new BasicOutputBuffer())); + return DualMessageSequences.WritersProviderAndLimitsChecker.WriteResult.OK_LIMIT_NOT_REACHED; + }); + return toBsonDocument(serverResponse); + }).when(connection).command( + anyString(), + any(BsonDocument.class), + any(), + isNull(), + any(), + any(OperationContext.class), + anyBoolean(), + any(DualMessageSequences.class) + ); + } + + private static BsonDocument toBsonDocument(final String serverResponse) { + Codec bsonDocumentCodec = + CommandResultDocumentCodec.create(getDefaultCodecRegistry().get(BsonDocument.class), CommandBatchCursorHelper.FIRST_BATCH); + return bsonDocumentCodec.decode(new JsonReader(serverResponse), DecoderContext.builder().build()); + } +} From 69a3c1d789271eeee0f5495bd4b6aab2774fec64 Mon Sep 17 00:00:00 2001 From: Almas Abdrazak Date: Fri, 5 Dec 2025 15:23:59 -0800 Subject: [PATCH 03/60] evergreen git clone depth is 1 (#1851) Co-authored-by: Almas Abdrazak --- .evergreen/.evg.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.evergreen/.evg.yml b/.evergreen/.evg.yml index c6b28f9a6f6..968db1b3c97 100644 --- a/.evergreen/.evg.yml +++ b/.evergreen/.evg.yml @@ -33,6 +33,7 @@ functions: - command: git.get_project params: directory: "src" + clone_depth: 1 # Applies the subitted patch, if any # Deprecated. Should be removed. But still needed for certain agents (ZAP) - command: git.apply_patch From 5cb23ba8e2a9da83857f3e2f7bbb7e5cfc8dcbe2 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Mon, 15 Dec 2025 13:24:37 +0000 Subject: [PATCH 04/60] Updated specifications submodule to latest Disabled new test cases until the work is done. JAVA-5949 JAVA-5968 JAVA-6030 --- driver-core/src/test/resources/specifications | 2 +- .../com/mongodb/AuthConnectionStringTest.java | 4 ++++ .../ServerDiscoveryAndMonitoringTest.java | 3 +++ .../unified/UnifiedTestModifications.java | 18 ++++++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/driver-core/src/test/resources/specifications b/driver-core/src/test/resources/specifications index ace53b165f2..a8d34be0df2 160000 --- a/driver-core/src/test/resources/specifications +++ b/driver-core/src/test/resources/specifications @@ -1 +1 @@ -Subproject commit ace53b165f2ab83e8385de15fbda9346befc0ea7 +Subproject commit a8d34be0df234365600a9269af5a463f581562fd diff --git a/driver-core/src/test/unit/com/mongodb/AuthConnectionStringTest.java b/driver-core/src/test/unit/com/mongodb/AuthConnectionStringTest.java index f214667e510..44f24042f78 100644 --- a/driver-core/src/test/unit/com/mongodb/AuthConnectionStringTest.java +++ b/driver-core/src/test/unit/com/mongodb/AuthConnectionStringTest.java @@ -56,6 +56,10 @@ public void shouldPassAllOutcomes() { assumeFalse(description.equals("should accept forwardAndReverse hostname canonicalization (GSSAPI)")); assumeFalse(description.equals("should accept generic mechanism property (GSSAPI)")); assumeFalse(description.equals("should accept no hostname canonicalization (GSSAPI)")); + assumeFalse("https://jira.mongodb.org/browse/JAVA-6030", + description.equals("should throw an exception if AWS_SESSION_TOKEN provided (MONGODB-AWS)")); + assumeFalse("https://jira.mongodb.org/browse/JAVA-6030", + description.equals("should throw an exception if username and password provided (MONGODB-AWS)")); if (definition.getBoolean("valid").getValue()) { testValidUris(); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java index 2a70deaf90d..dc81e5071e1 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDiscoveryAndMonitoringTest.java @@ -54,6 +54,9 @@ public class ServerDiscoveryAndMonitoringTest extends AbstractServerDiscoveryAnd public ServerDiscoveryAndMonitoringTest(final String description, final BsonDocument definition) { super(definition); + assumeFalse("https://jira.mongodb.org/browse/JAVA-5949", + description.equals("error_handling_handshake.json: Network timeouts before and after the handshake completes")); + this.description = description; init(serverAddress -> NO_OP_SERVER_LISTENER, NO_OP_CLUSTER_LISTENER); } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index dc39f14a6c6..e63cf1490c4 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -420,6 +420,24 @@ public static void applyCustomizations(final TestDef def) { .file("server-discovery-and-monitoring", "pool-clear-on-error-checkout"); def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") .file("server-discovery-and-monitoring", "pool-cleared-on-min-pool-size-population-error"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") + .file("server-discovery-and-monitoring", "backpressure-network-error-fail"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") + .file("server-discovery-and-monitoring", "backpressure-network-timeout-error"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") + .file("server-discovery-and-monitoring", "backpressure-server-description-unchanged-on-min-pool-size-population-error"); + + // session tests + def.skipJira("https://jira.mongodb.org/browse/JAVA-5968") + .test("sessions", "snapshot-sessions", "Find operation with snapshot and snapshot time"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5968") + .test("sessions", "snapshot-sessions", "Distinct operation with snapshot and snapshot time"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5968") + .test("sessions", "snapshot-sessions", "Aggregate operation with snapshot and snapshot time"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5968") + .test("sessions", "snapshot-sessions", "countDocuments operation with snapshot and snapshot time"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5968") + .test("sessions", "snapshot-sessions", "Mixed operation with snapshot and snapshotTime"); // transactions From 07a7357051ab38dee9ad3a87741b20c1b3844af0 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 18 Dec 2025 14:46:10 +0000 Subject: [PATCH 05/60] Fixes JAVA-6023, align log level to warning from error when removing the server from the cluster (#1853) --- .../connection/AbstractMultiServerCluster.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java b/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java index acaf1a40e14..11abddbb97a 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java @@ -231,7 +231,7 @@ public void onChange(final ServerDescriptionChangedEvent event) { private boolean handleReplicaSetMemberChanged(final ServerDescription newDescription) { if (!newDescription.isReplicaSetMember()) { - LOGGER.error(format("Expecting replica set member, but found a %s. Removing %s from client view of cluster.", + LOGGER.warn(format("Expecting replica set member, but found a %s. Removing %s from client view of cluster.", newDescription.getType(), newDescription.getAddress())); removeServer(newDescription.getAddress()); return true; @@ -247,7 +247,7 @@ private boolean handleReplicaSetMemberChanged(final ServerDescription newDescrip } if (!replicaSetName.equals(newDescription.getSetName())) { - LOGGER.error(format("Expecting replica set member from set '%s', but found one from set '%s'. " + LOGGER.warn(format("Expecting replica set member from set '%s', but found one from set '%s'. " + "Removing %s from client view of cluster.", replicaSetName, newDescription.getSetName(), newDescription.getAddress())); removeServer(newDescription.getAddress()); @@ -259,7 +259,7 @@ private boolean handleReplicaSetMemberChanged(final ServerDescription newDescrip if (newDescription.getCanonicalAddress() != null && !newDescription.getAddress().equals(new ServerAddress(newDescription.getCanonicalAddress())) && !newDescription.isPrimary()) { - LOGGER.info(format("Canonical address %s does not match server address. Removing %s from client view of cluster", + LOGGER.warn(format("Canonical address %s does not match server address. Removing %s from client view of cluster", newDescription.getCanonicalAddress(), newDescription.getAddress())); removeServer(newDescription.getAddress()); return true; @@ -342,7 +342,7 @@ private boolean isNotAlreadyPrimary(final ServerAddress address) { private boolean handleShardRouterChanged(final ServerDescription newDescription) { if (!newDescription.isShardRouter()) { - LOGGER.error(format("Expecting a %s, but found a %s. Removing %s from client view of cluster.", + LOGGER.warn(format("Expecting a %s, but found a %s. Removing %s from client view of cluster.", SHARD_ROUTER, newDescription.getType(), newDescription.getAddress())); removeServer(newDescription.getAddress()); } @@ -351,7 +351,7 @@ private boolean handleShardRouterChanged(final ServerDescription newDescription) private boolean handleStandAloneChanged(final ServerDescription newDescription) { if (getSettings().getHosts().size() > 1) { - LOGGER.error(format("Expecting a single %s, but found more than one. Removing %s from client view of cluster.", + LOGGER.warn(format("Expecting a single %s, but found more than one. Removing %s from client view of cluster.", STANDALONE, newDescription.getAddress())); clusterType = UNKNOWN; removeServer(newDescription.getAddress()); From 74084934756bfc88d02fa1243e75e0d793c8bea1 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Thu, 18 Dec 2025 08:11:35 -0800 Subject: [PATCH 06/60] Reuse ConnectionSource to avoid extra server selection. (#1813) JAVA-5974 --------- Co-authored-by: Ross Lawley --- .../AsyncChangeStreamBatchCursor.java | 55 ++++++++- .../operation/ChangeStreamBatchCursor.java | 51 ++++++++- .../ChangeStreamBatchCursorTest.java | 27 +++-- .../client/ChangeStreamFunctionalTest.java | 29 +++++ .../AbstractChangeSteamFunctionalTest.java | 105 ++++++++++++++++++ .../client/ChangeSteamFunctionalTest.java | 27 +++++ 6 files changed, 281 insertions(+), 13 deletions(-) create mode 100644 driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ChangeStreamFunctionalTest.java create mode 100644 driver-sync/src/test/functional/com/mongodb/client/AbstractChangeSteamFunctionalTest.java create mode 100644 driver-sync/src/test/functional/com/mongodb/client/ChangeSteamFunctionalTest.java diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java index a144888f859..ce7127e0dc3 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncChangeStreamBatchCursor.java @@ -18,9 +18,12 @@ import com.mongodb.MongoException; +import com.mongodb.ReadPreference; +import com.mongodb.assertions.Assertions; import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.async.AsyncAggregateResponseBatchCursor; import com.mongodb.internal.async.SingleResultCallback; +import com.mongodb.internal.binding.AsyncConnectionSource; import com.mongodb.internal.binding.AsyncReadBinding; import com.mongodb.internal.connection.OperationContext; import com.mongodb.lang.NonNull; @@ -232,8 +235,9 @@ private void retryOperation(final AsyncBlock asyncBlock, } else { changeStreamOperation.setChangeStreamOptionsForResume(resumeToken, assertNotNull(source).getServerDescription().getMaxWireVersion()); - source.release(); - changeStreamOperation.executeAsync(binding, operationContext, (asyncBatchCursor, t1) -> { + // We wrap the binding so that the selected AsyncConnectionSource is reused, preventing redundant server selection. + // Consequently, the same AsyncConnectionSource remains pinned to the resulting AsyncCommandCursor. + changeStreamOperation.executeAsync(new AsyncSourceAwareReadBinding(source, binding), operationContext, (asyncBatchCursor, t1) -> { if (t1 != null) { callback.onResult(null, t1); } else { @@ -242,6 +246,7 @@ private void retryOperation(final AsyncBlock asyncBlock, operationContext); } finally { try { + source.release(); binding.release(); // release the new change stream batch cursor's reference to the binding } finally { resumeableOperation(asyncBlock, callback, operationContext, tryNext); @@ -252,5 +257,51 @@ private void retryOperation(final AsyncBlock asyncBlock, } }); } + + /** + * Does not retain wrapped {@link AsyncReadBinding} as it serves as a wrapper only. + */ + private static class AsyncSourceAwareReadBinding implements AsyncReadBinding { + private final AsyncConnectionSource source; + private final AsyncReadBinding binding; + + AsyncSourceAwareReadBinding(final AsyncConnectionSource source, final AsyncReadBinding binding) { + this.source = source; + this.binding = binding; + } + + @Override + public ReadPreference getReadPreference() { + return binding.getReadPreference(); + } + + @Override + public void getReadConnectionSource(final OperationContext operationContext, final SingleResultCallback callback) { + source.retain(); + callback.onResult(source, null); + } + + @Override + public void getReadConnectionSource(final int minWireVersion, final ReadPreference fallbackReadPreference, + final OperationContext operationContext, + final SingleResultCallback callback) { + throw Assertions.fail(); + } + + @Override + public AsyncReadBinding retain() { + return binding.retain(); + } + + @Override + public int release() { + return binding.release(); + } + + @Override + public int getCount() { + return binding.getCount(); + } + } } diff --git a/driver-core/src/main/com/mongodb/internal/operation/ChangeStreamBatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/ChangeStreamBatchCursor.java index a750637a10e..cf9f1dcf6c4 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ChangeStreamBatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ChangeStreamBatchCursor.java @@ -19,9 +19,12 @@ import com.mongodb.MongoChangeStreamException; import com.mongodb.MongoException; import com.mongodb.MongoOperationTimeoutException; +import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; import com.mongodb.ServerCursor; +import com.mongodb.assertions.Assertions; import com.mongodb.internal.TimeoutContext; +import com.mongodb.internal.binding.ConnectionSource; import com.mongodb.internal.binding.ReadBinding; import com.mongodb.internal.connection.OperationContext; import com.mongodb.lang.Nullable; @@ -251,9 +254,11 @@ private void resumeChangeStream(final OperationContext operationContext) { wrapped.close(operationContextWithDefaultMaxTime); withReadConnectionSource(binding, operationContext, (source, operationContextWithMinRtt) -> { changeStreamOperation.setChangeStreamOptionsForResume(resumeToken, source.getServerDescription().getMaxWireVersion()); + // We wrap the binding so that the selected ConnectionSource is reused, preventing redundant server selection. + // Consequently, the same ConnectionSource remains pinned to the resulting CommandCursor. + wrapped = ((ChangeStreamBatchCursor) changeStreamOperation.execute(new SourceAwareReadBinding(source, binding), operationContextWithDefaultMaxTime)).getWrapped(); return null; }); - wrapped = ((ChangeStreamBatchCursor) changeStreamOperation.execute(binding, operationContextWithDefaultMaxTime)).getWrapped(); binding.release(); // release the new change stream batch cursor's reference to the binding } @@ -264,4 +269,48 @@ private boolean hasPreviousNextTimedOut() { private static boolean isTimeoutException(final Throwable exception) { return exception instanceof MongoOperationTimeoutException; } + + /** + * Does not retain wrapped {link @ReadBinding} as it serves as a wrapper only. + */ + private static class SourceAwareReadBinding implements ReadBinding { + private final ConnectionSource source; + private final ReadBinding binding; + + SourceAwareReadBinding(final ConnectionSource source, final ReadBinding binding) { + this.source = source; + this.binding = binding; + } + + @Override + public ReadPreference getReadPreference() { + return binding.getReadPreference(); + } + + @Override + public ConnectionSource getReadConnectionSource(final OperationContext ignored) { + source.retain(); + return source; + } + + @Override + public ConnectionSource getReadConnectionSource(final int minWireVersion, final ReadPreference fallbackReadPreference, final OperationContext ignored) { + throw Assertions.fail(); + } + + @Override + public int getCount() { + return binding.getCount(); + } + + @Override + public ReadBinding retain() { + return binding.retain(); + } + + @Override + public int release() { + return binding.release(); + } + } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/ChangeStreamBatchCursorTest.java b/driver-core/src/test/unit/com/mongodb/internal/operation/ChangeStreamBatchCursorTest.java index 3ce014986e8..947e125667a 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/ChangeStreamBatchCursorTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/ChangeStreamBatchCursorTest.java @@ -53,7 +53,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; @@ -216,7 +215,7 @@ void shouldResumeOnlyOnceOnSubsequentCallsAfterTimeoutError() { verify(newCursor).next(operationContextCaptor.capture())); verify(changeStreamOperation).setChangeStreamOptionsForResume(resumeToken, maxWireVersion); verify(changeStreamOperation, times(1)).getDecoder(); - verify(changeStreamOperation, times(1)).execute(eq(readBinding), any()); + verify(changeStreamOperation, times(1)).execute(any(ReadBinding.class), any()); verifyNoMoreInteractions(changeStreamOperation); verify(newCursor, times(1)).next(any()); verify(newCursor, atLeastOnce()).getPostBatchResumeToken(); @@ -245,7 +244,7 @@ void shouldResumeOnlyOnceOnSubsequentCallsAfterTimeoutError() { void shouldPropagateAnyErrorsOccurredInAggregateOperation() { when(cursor.next(any())).thenThrow(new MongoOperationTimeoutException("timeout")); MongoNotPrimaryException resumableError = new MongoNotPrimaryException(new BsonDocument(), new ServerAddress()); - when(changeStreamOperation.execute(eq(readBinding), any())).thenThrow(resumableError); + when(changeStreamOperation.execute(any(ReadBinding.class), any())).thenThrow(resumableError); ChangeStreamBatchCursor cursor = createChangeStreamCursor(); //when @@ -272,12 +271,12 @@ void shouldResumeAfterTimeoutInAggregateOnNextCall() { clearInvocations(this.cursor, newCursor, timeoutContext, changeStreamOperation, readBinding); //second next operation times out on resume attempt when creating change stream - when(changeStreamOperation.execute(eq(readBinding), any())).thenThrow( + when(changeStreamOperation.execute(any(ReadBinding.class), any())).thenThrow( new MongoOperationTimeoutException("timeout during resumption")); assertThrows(MongoOperationTimeoutException.class, cursor::next); - clearInvocations(this.cursor, newCursor, timeoutContext, changeStreamOperation); + clearInvocations(this.cursor, newCursor, timeoutContext, changeStreamOperation, readBinding); - doReturn(newChangeStreamCursor).when(changeStreamOperation).execute(eq(readBinding), any()); + doReturn(newChangeStreamCursor).when(changeStreamOperation).execute(any(ReadBinding.class), any()); //when third operation succeeds to resume and call next sleep(TIMEOUT_CONSUMPTION_SLEEP_MS); @@ -308,7 +307,7 @@ void shouldCloseChangeStreamWhenResumeOperationFailsDueToNonTimeoutError() { clearInvocations(this.cursor, newCursor, timeoutContext, changeStreamOperation, readBinding); //when second next operation errors on resume attempt when creating change stream - when(changeStreamOperation.execute(eq(readBinding), any())).thenThrow( + when(changeStreamOperation.execute(any(ReadBinding.class), any())).thenThrow( new MongoNotPrimaryException(new BsonDocument(), new ServerAddress())); assertThrows(MongoNotPrimaryException.class, cursor::next); @@ -344,7 +343,11 @@ private void verifyNoResumeAttemptCalled() { private void verifyResumeAttemptCalled() { verify(cursor, times(1)).close(any()); verify(changeStreamOperation).setChangeStreamOptionsForResume(resumeToken, maxWireVersion); - verify(changeStreamOperation, times(1)).execute(eq(readBinding), any()); + verify(changeStreamOperation, times(1)).execute(any(ReadBinding.class), any()); + verifyNoMoreInteractions(cursor); + verify(changeStreamOperation, times(1)).execute(any(ReadBinding.class), any()); + // Verify server selection is done once for the resume attempt. + verify(readBinding, times(1)).getReadConnectionSource(any()); verifyNoMoreInteractions(cursor); } @@ -394,10 +397,14 @@ void setUp() { changeStreamOperation = mock(ChangeStreamOperation.class); when(changeStreamOperation.getDecoder()).thenReturn(new DocumentCodec()); doNothing().when(changeStreamOperation).setChangeStreamOptionsForResume(resumeToken, maxWireVersion); - when(changeStreamOperation.execute(eq(readBinding), any())).thenReturn(newChangeStreamCursor); + when(changeStreamOperation.execute(any(ReadBinding.class), any())).thenAnswer(invocation -> { + ReadBinding binding = invocation.getArgument(0); + OperationContext operationContext = invocation.getArgument(1); + binding.getReadConnectionSource(operationContext); + return newChangeStreamCursor; + }); } - private void assertTimeoutWasRefreshedForOperation(final TimeoutContext timeoutContextUsedForOperation) { assertNotNull(timeoutContextUsedForOperation.getTimeout(), "TimeoutMs was not set"); timeoutContextUsedForOperation.getTimeout().run(TimeUnit.MILLISECONDS, () -> { diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ChangeStreamFunctionalTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ChangeStreamFunctionalTest.java new file mode 100644 index 00000000000..3b204150019 --- /dev/null +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ChangeStreamFunctionalTest.java @@ -0,0 +1,29 @@ +/* + * 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; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.AbstractChangeSteamFunctionalTest; +import com.mongodb.client.MongoClient; +import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; + +public class ChangeStreamFunctionalTest extends AbstractChangeSteamFunctionalTest { + @Override + protected MongoClient createMongoClient(final MongoClientSettings mongoClientSettings) { + return new SyncMongoClient(mongoClientSettings); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractChangeSteamFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractChangeSteamFunctionalTest.java new file mode 100644 index 00000000000..b982f762f07 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractChangeSteamFunctionalTest.java @@ -0,0 +1,105 @@ +/* + * 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; + +import com.mongodb.ClusterFixture; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoNamespace; +import com.mongodb.client.model.changestream.ChangeStreamDocument; +import com.mongodb.client.test.CollectionHelper; +import org.bson.BsonDocument; +import org.bson.BsonTimestamp; +import org.bson.Document; +import org.bson.codecs.BsonDocumentCodec; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.mongodb.client.Fixture.getDefaultDatabaseName; +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * The {@link ChangeStreamProseTest}, which is defined only for sync driver, should be migrated to this class. + * Once this done, this class should be renamed to ChangeStreamProseTest. + */ +public abstract class AbstractChangeSteamFunctionalTest { + + private static final String FAIL_COMMAND_NAME = "failCommand"; + private static final MongoNamespace NAMESPACE = new MongoNamespace(getDefaultDatabaseName(), "test"); + private final CollectionHelper collectionHelper = new CollectionHelper<>(new BsonDocumentCodec(), NAMESPACE); + + protected abstract MongoClient createMongoClient(MongoClientSettings mongoClientSettings); + + @Test + public void shouldDoOneServerSelectionForResumeAttempt() { + //given + assumeTrue(ClusterFixture.isDiscoverableReplicaSet()); + AtomicInteger serverSelectionCounter = new AtomicInteger(); + BsonTimestamp startTime = new BsonTimestamp((int) Instant.now().getEpochSecond(), 0); + try (MongoClient mongoClient = createMongoClient(Fixture.getMongoClientSettingsBuilder() + .applyToClusterSettings(builder -> builder.serverSelector(clusterDescription -> { + serverSelectionCounter.incrementAndGet(); + return clusterDescription.getServerDescriptions(); + })).build())) { + + MongoCollection collection = mongoClient + .getDatabase(NAMESPACE.getDatabaseName()) + .getCollection(NAMESPACE.getCollectionName()); + + collectionHelper.runAdminCommand("{" + + " configureFailPoint: \"" + FAIL_COMMAND_NAME + "\"," + + " mode: {" + + " times: 1" + + " }," + + " data: {" + + " failCommands: ['getMore']," + + " errorCode: 9001," + + " errorLabels: ['ResumableChangeStreamError']" + + " }" + + "}"); + // We insert document here, because async cursor performs aggregate and getMore right after we call cursor() + collection.insertOne(Document.parse("{ x: 1 }")); + serverSelectionCounter.set(0); + + try (MongoChangeStreamCursor> cursor = collection.watch() + .batchSize(0) + .startAtOperationTime(startTime) + .cursor()) { + + //when + ChangeStreamDocument changeStreamDocument = cursor.next(); + //then + assertNotNull(changeStreamDocument); + int actualCountOfServerSelections = serverSelectionCounter.get(); + assertEquals(2, actualCountOfServerSelections, + format("Expected 2 server selections (initial aggregate command + resume attempt aggregate command), but there were %s", + actualCountOfServerSelections)); + } + } + } + + @AfterEach + public void tearDown() throws InterruptedException { + ClusterFixture.disableFailPoint(FAIL_COMMAND_NAME); + collectionHelper.drop(); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/ChangeSteamFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/ChangeSteamFunctionalTest.java new file mode 100644 index 00000000000..c4ec6286ce2 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/ChangeSteamFunctionalTest.java @@ -0,0 +1,27 @@ +/* + * 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; + +import com.mongodb.MongoClientSettings; + +public class ChangeSteamFunctionalTest extends AbstractChangeSteamFunctionalTest { + + @Override + protected MongoClient createMongoClient(final MongoClientSettings mongoClientSettings) { + return MongoClients.create(mongoClientSettings); + } +} From cece170b9f29384aa86def743ee10b3bad565baf Mon Sep 17 00:00:00 2001 From: Almas Abdrazak Date: Fri, 2 Jan 2026 21:39:05 -0800 Subject: [PATCH 07/60] JAVA-4320 fix server selection flaky test (#1847) * JAVA-4320 fix server selection flaky test * set server selection threshold to 3seconds * serverSelection flaky test, update comments * server selection , update threshold to 30_000 MS --------- Co-authored-by: Almas Abdrazak --- .../com/mongodb/client/AbstractServerSelectionProseTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java index 506a40d8bd6..45cc8c28aa2 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java @@ -20,6 +20,9 @@ import com.mongodb.ServerAddress; import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.TestCommandListener; + +import java.util.concurrent.TimeUnit; + import org.bson.BsonArray; import org.bson.BsonBoolean; import org.bson.BsonDocument; @@ -77,6 +80,7 @@ void operationCountBasedSelectionWithinLatencyWindow() throws InterruptedExcepti MongoClientSettings clientSettings = getMongoClientSettingsBuilder() .applicationName(appName) .applyConnectionString(multiMongosConnectionString) + .applyToClusterSettings(builder -> builder.localThreshold(30_000L, TimeUnit.MILLISECONDS)) .applyToConnectionPoolSettings(builder -> builder .minSize(tasks)) .addCommandListener(commandListener) From 139eee0e72cde0d990e50c2cb21b53a9f16e6e24 Mon Sep 17 00:00:00 2001 From: Almas Abdrazak Date: Mon, 5 Jan 2026 12:01:55 -0800 Subject: [PATCH 08/60] encryptionClient uses writeConcern majority (#1858) Co-authored-by: Almas Abdrazak --- .../AbstractClientSideEncryptionDecryptionEventsTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionDecryptionEventsTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionDecryptionEventsTest.java index 24fbf17779a..0fc82dea4eb 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionDecryptionEventsTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionDecryptionEventsTest.java @@ -24,6 +24,7 @@ import com.mongodb.MongoClientSettings; import com.mongodb.MongoCommandException; import com.mongodb.MongoSocketReadException; +import com.mongodb.WriteConcern; import com.mongodb.client.model.Aggregates; import com.mongodb.client.model.vault.EncryptOptions; import com.mongodb.client.vault.ClientEncryption; @@ -106,6 +107,7 @@ public void setUp() { .kmsProviders(kmsProviders) .build()) .retryReads(false) + .writeConcern(WriteConcern.MAJORITY) .addCommandListener(commandListener) .build()); } From 7200ed9c08d80f90c4b28899d8d12c0ea299c0af Mon Sep 17 00:00:00 2001 From: Almas Abdrazak Date: Tue, 6 Jan 2026 08:35:50 -0800 Subject: [PATCH 09/60] [JAVA-6043](https://jira.mongodb.org/browse/JAVA-6043) (#1859) --- .../functional/com/mongodb/client/TransactionProseTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/driver-sync/src/test/functional/com/mongodb/client/TransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/TransactionProseTest.java index 57070f98dd9..0c0c9c5d082 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/TransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/TransactionProseTest.java @@ -21,6 +21,9 @@ import com.mongodb.MongoException; import com.mongodb.ServerAddress; import com.mongodb.WriteConcern; + +import java.util.concurrent.TimeUnit; + import org.bson.Document; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -57,6 +60,8 @@ public void setUp() { } client = MongoClients.create(MongoClientSettings.builder() + .writeConcern(WriteConcern.MAJORITY) + .applyToClusterSettings(builder -> builder.localThreshold(1000L, TimeUnit.MILLISECONDS)) .applyConnectionString(connectionString) .build()); From 6fd2c950d82f53a1322dac68719d761a54efeee1 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Thu, 15 Jan 2026 14:06:52 -0800 Subject: [PATCH 10/60] Handle unexpected end of stream errors from KMS. (#1849) JAVA-6015 --- .evergreen/run-kms-tls-tests.sh | 8 ++ .../internal/crypt/KeyManagementService.java | 6 ++ .../com/mongodb/client/internal/Crypt.java | 5 +- ...bstractClientSideEncryptionKmsTlsTest.java | 84 +++++++++++++++++-- .../auth/AbstractX509AuthenticationTest.java | 27 +----- .../mongodb/fixture/EncryptionFixture.java | 39 +++++++++ 6 files changed, 138 insertions(+), 31 deletions(-) diff --git a/.evergreen/run-kms-tls-tests.sh b/.evergreen/run-kms-tls-tests.sh index df3a38c0eec..bdc716fc86f 100755 --- a/.evergreen/run-kms-tls-tests.sh +++ b/.evergreen/run-kms-tls-tests.sh @@ -17,6 +17,12 @@ echo "Running KMS TLS tests" cp ${JAVA_HOME}/lib/security/cacerts mongo-truststore ${JAVA_HOME}/bin/keytool -importcert -trustcacerts -file ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -keystore mongo-truststore -storepass changeit -storetype JKS -noprompt +# Create keystore from server.pem to emulate KMS server in tests. +openssl pkcs12 -export \ + -in ${DRIVERS_TOOLS}/.evergreen/x509gen/server.pem \ + -out server.p12 \ + -password pass:test + export GRADLE_EXTRA_VARS="-Pssl.enabled=true -Pssl.trustStoreType=jks -Pssl.trustStore=`pwd`/mongo-truststore -Pssl.trustStorePassword=changeit" export KMS_TLS_ERROR_TYPE=${KMS_TLS_ERROR_TYPE} @@ -24,12 +30,14 @@ export KMS_TLS_ERROR_TYPE=${KMS_TLS_ERROR_TYPE} ./gradlew --stacktrace --info ${GRADLE_EXTRA_VARS} -Dorg.mongodb.test.uri=${MONGODB_URI} \ -Dorg.mongodb.test.kms.tls.error.type=${KMS_TLS_ERROR_TYPE} \ + -Dorg.mongodb.test.kms.keystore.location="$(pwd)" \ driver-sync:cleanTest driver-sync:test --tests ClientSideEncryptionKmsTlsTest first=$? echo $first ./gradlew --stacktrace --info ${GRADLE_EXTRA_VARS} -Dorg.mongodb.test.uri=${MONGODB_URI} \ -Dorg.mongodb.test.kms.tls.error.type=${KMS_TLS_ERROR_TYPE} \ + -Dorg.mongodb.test.kms.keystore.location="$(pwd)" \ driver-reactive-streams:cleanTest driver-reactive-streams:test --tests ClientSideEncryptionKmsTlsTest second=$? echo $second diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java index b82dd590618..67ebf421c9c 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/KeyManagementService.java @@ -16,6 +16,7 @@ package com.mongodb.reactivestreams.client.internal.crypt; +import com.mongodb.MongoException; import com.mongodb.MongoOperationTimeoutException; import com.mongodb.MongoSocketException; import com.mongodb.MongoSocketReadTimeoutException; @@ -131,6 +132,11 @@ private void streamRead(final Stream stream, final MongoKeyDecryptor keyDecrypto @Override public void completed(final Integer integer, final Void aVoid) { + if (integer == -1) { + sink.error(new MongoException( + "Unexpected end of stream from KMS provider " + keyDecryptor.getKmsProvider())); + return; + } buffer.flip(); try { keyDecryptor.feed(buffer.asNIO()); diff --git a/driver-sync/src/main/com/mongodb/client/internal/Crypt.java b/driver-sync/src/main/com/mongodb/client/internal/Crypt.java index ae7a75ae626..67fac13770c 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/Crypt.java +++ b/driver-sync/src/main/com/mongodb/client/internal/Crypt.java @@ -369,6 +369,9 @@ private void decryptKey(final MongoKeyDecryptor keyDecryptor, @Nullable final Ti while (bytesNeeded > 0) { byte[] bytes = new byte[bytesNeeded]; int bytesRead = inputStream.read(bytes, 0, bytes.length); + if (bytesRead == -1) { + throw new MongoException("Unexpected end of stream from KMS provider " + keyDecryptor.getKmsProvider()); + } keyDecryptor.feed(ByteBuffer.wrap(bytes, 0, bytesRead)); bytesNeeded = keyDecryptor.bytesNeeded(); } @@ -376,7 +379,7 @@ private void decryptKey(final MongoKeyDecryptor keyDecryptor, @Nullable final Ti } private MongoException wrapInMongoException(final Throwable t) { - if (t instanceof MongoException) { + if (t instanceof MongoClientException) { return (MongoException) t; } else { return new MongoClientException("Exception in encryption library: " + t.getMessage(), t); diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionKmsTlsTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionKmsTlsTest.java index 6e0b5957dea..3c307aa468c 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionKmsTlsTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionKmsTlsTest.java @@ -20,14 +20,19 @@ import com.mongodb.MongoClientException; import com.mongodb.client.model.vault.DataKeyOptions; import com.mongodb.client.vault.ClientEncryption; +import com.mongodb.fixture.EncryptionFixture; import com.mongodb.lang.NonNull; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.junit.jupiter.api.Test; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import java.io.IOException; +import java.net.Socket; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; @@ -35,16 +40,20 @@ import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import static com.mongodb.ClusterFixture.getEnv; import static com.mongodb.ClusterFixture.hasEncryptionTestsEnabled; import static com.mongodb.client.Fixture.getMongoClientSettings; import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; + public abstract class AbstractClientSideEncryptionKmsTlsTest { private static final String SYSTEM_PROPERTY_KEY = "org.mongodb.test.kms.tls.error.type"; @@ -128,7 +137,7 @@ public void testInvalidKmsCertificate() { * See * 11. KMS TLS Options Tests. */ - @Test() + @Test public void testThatCustomSslContextIsUsed() { assumeTrue(hasEncryptionTestsEnabled()); @@ -165,34 +174,71 @@ public void testThatCustomSslContextIsUsed() { } } + /** + * Not a prose spec test. However, it is additional test case for better coverage. + */ + @Test + public void testUnexpectedEndOfStreamFromKmsProvider() throws Exception { + String kmsKeystoreLocation = System.getProperty("org.mongodb.test.kms.keystore.location"); + assumeTrue(kmsKeystoreLocation != null && !kmsKeystoreLocation.isEmpty(), + "System property org.mongodb.test.kms.keystore.location is not set"); + + int kmsPort = 5555; + ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() + .keyVaultMongoClientSettings(getMongoClientSettings()) + .keyVaultNamespace("keyvault.datakeys") + .kmsProviders(new HashMap>() {{ + put("kmip", new HashMap() {{ + put("endpoint", "localhost:" + kmsPort); + }}); + }}) + .build(); + + Thread serverThread = null; + try (ClientEncryption clientEncryption = getClientEncryption(clientEncryptionSettings)) { + serverThread = startKmsServerSimulatingEof(EncryptionFixture.buildSslContextFromKeyStore( + kmsKeystoreLocation, + "server.p12"), kmsPort); + + MongoClientException mongoException = assertThrows(MongoClientException.class, + () -> clientEncryption.createDataKey("kmip", new DataKeyOptions())); + assertEquals("Exception in encryption library: Unexpected end of stream from KMS provider kmip", + mongoException.getMessage()); + } finally { + if (serverThread != null) { + serverThread.interrupt(); + } + } + } + private HashMap> getKmsProviders() { return new HashMap>() {{ - put("aws", new HashMap() {{ + put("aws", new HashMap() {{ put("accessKeyId", getEnv("AWS_ACCESS_KEY_ID")); put("secretAccessKey", getEnv("AWS_SECRET_ACCESS_KEY")); }}); - put("aws:named", new HashMap() {{ + put("aws:named", new HashMap() {{ put("accessKeyId", getEnv("AWS_ACCESS_KEY_ID")); put("secretAccessKey", getEnv("AWS_SECRET_ACCESS_KEY")); }}); - put("azure", new HashMap() {{ + put("azure", new HashMap() {{ put("tenantId", getEnv("AZURE_TENANT_ID")); put("clientId", getEnv("AZURE_CLIENT_ID")); put("clientSecret", getEnv("AZURE_CLIENT_SECRET")); put("identityPlatformEndpoint", "login.microsoftonline.com:443"); }}); - put("azure:named", new HashMap() {{ + put("azure:named", new HashMap() {{ put("tenantId", getEnv("AZURE_TENANT_ID")); put("clientId", getEnv("AZURE_CLIENT_ID")); put("clientSecret", getEnv("AZURE_CLIENT_SECRET")); put("identityPlatformEndpoint", "login.microsoftonline.com:443"); }}); - put("gcp", new HashMap() {{ + put("gcp", new HashMap() {{ put("email", getEnv("GCP_EMAIL")); put("privateKey", getEnv("GCP_PRIVATE_KEY")); put("endpoint", "oauth2.googleapis.com:443"); }}); - put("gcp:named", new HashMap() {{ + put("gcp:named", new HashMap() {{ put("email", getEnv("GCP_EMAIL")); put("privateKey", getEnv("GCP_PRIVATE_KEY")); put("endpoint", "oauth2.googleapis.com:443"); @@ -257,5 +303,29 @@ public void checkServerTrusted(final X509Certificate[] certs, final String authT throw new RuntimeException(e); } } + + private Thread startKmsServerSimulatingEof(final SSLContext sslContext, final int kmsPort) + throws Exception { + CompletableFuture confirmListening = new CompletableFuture<>(); + Thread serverThread = new Thread(() -> { + try { + SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory(); + try (SSLServerSocket sslServerSocket = (SSLServerSocket) serverSocketFactory.createServerSocket(kmsPort)) { + sslServerSocket.setNeedClientAuth(false); + confirmListening.complete(null); + try (Socket accept = sslServerSocket.accept()) { + accept.setSoTimeout(10000); + accept.getInputStream().read(); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + }, "KMIP-EOF-Fake-Server"); + serverThread.setDaemon(true); + serverThread.start(); + confirmListening.get(TimeUnit.SECONDS.toMillis(10), TimeUnit.MILLISECONDS); + return serverThread; + } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/auth/AbstractX509AuthenticationTest.java b/driver-sync/src/test/functional/com/mongodb/client/auth/AbstractX509AuthenticationTest.java index 0d003210f3d..e325e9a23f8 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/auth/AbstractX509AuthenticationTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/auth/AbstractX509AuthenticationTest.java @@ -22,6 +22,7 @@ import com.mongodb.client.Fixture; import com.mongodb.client.MongoClient; import com.mongodb.connection.NettyTransportSettings; +import com.mongodb.fixture.EncryptionFixture; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import org.junit.jupiter.api.extension.ConditionEvaluationResult; @@ -34,14 +35,6 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; import java.util.stream.Stream; import static com.mongodb.AuthenticationMechanism.MONGODB_X509; @@ -52,7 +45,6 @@ @ExtendWith(AbstractX509AuthenticationTest.X509AuthenticationPropertyCondition.class) public abstract class AbstractX509AuthenticationTest { - private static final String KEYSTORE_PASSWORD = "test"; protected abstract MongoClient createMongoClient(MongoClientSettings mongoClientSettings); private static Stream shouldAuthenticateWithClientCertificate() throws Exception { @@ -128,22 +120,11 @@ private static Stream getArgumentForKeystore(final String keystoreFil } private static SSLContext buildSslContextFromKeyStore(final String keystoreFileName) throws Exception { - KeyManagerFactory keyManagerFactory = getKeyManagerFactory(keystoreFileName); - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyManagerFactory.getKeyManagers(), null, null); - return sslContext; + return EncryptionFixture.buildSslContextFromKeyStore(getKeystoreLocation(), keystoreFileName); } - private static KeyManagerFactory getKeyManagerFactory(final String keystoreFileName) - throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { - KeyStore ks = KeyStore.getInstance("PKCS12"); - try (FileInputStream fis = new FileInputStream(getKeystoreLocation() + File.separator + keystoreFileName)) { - ks.load(fis, KEYSTORE_PASSWORD.toCharArray()); - } - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( - KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(ks, KEYSTORE_PASSWORD.toCharArray()); - return keyManagerFactory; + private static KeyManagerFactory getKeyManagerFactory(final String keystoreFileName) throws Exception { + return EncryptionFixture.getKeyManagerFactory(getKeystoreLocation(), keystoreFileName); } private static String getKeystoreLocation() { diff --git a/driver-sync/src/test/functional/com/mongodb/fixture/EncryptionFixture.java b/driver-sync/src/test/functional/com/mongodb/fixture/EncryptionFixture.java index f6edb9a14ed..e69864980a2 100644 --- a/driver-sync/src/test/functional/com/mongodb/fixture/EncryptionFixture.java +++ b/driver-sync/src/test/functional/com/mongodb/fixture/EncryptionFixture.java @@ -18,6 +18,11 @@ package com.mongodb.fixture; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import java.io.File; +import java.io.FileInputStream; +import java.security.KeyStore; import java.util.HashMap; import java.util.Map; @@ -28,6 +33,8 @@ */ public final class EncryptionFixture { + private static final String KEYSTORE_PASSWORD = "test"; + private EncryptionFixture() { //NOP } @@ -73,6 +80,38 @@ public static Map> getKmsProviders(final KmsProvider }}; } + /** + * Creates a {@link KeyManagerFactory} from a PKCS12 keystore file for use in TLS connections. + * The keystore is loaded using the password {@value #KEYSTORE_PASSWORD}. + * + * @return a {@link KeyManagerFactory initialized with the keystore's key material + */ + public static KeyManagerFactory getKeyManagerFactory(final String keystoreLocation, final String keystoreFileName) throws Exception { + KeyStore ks = KeyStore.getInstance("PKCS12"); + try (FileInputStream fis = new FileInputStream(keystoreLocation + File.separator + keystoreFileName)) { + ks.load(fis, KEYSTORE_PASSWORD.toCharArray()); + } + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(ks, KEYSTORE_PASSWORD.toCharArray()); + return keyManagerFactory; + } + + /** + * Creates an {@link SSLContext} from a PKCS12 keystore file for TLS connections. + * + * Allows configuring MongoClient with a custom {@link SSLContext} to test scenarios like TLS connections using specific certificates + * (e.g., expired or invalid) and setting up KMS servers. + * + * @return an initialized {@link SSLContext} configured with the keystore's key material + * @see #getKeyManagerFactory + */ + public static SSLContext buildSslContextFromKeyStore(final String keystoreLocation, final String keystoreFileName) throws Exception { + KeyManagerFactory keyManagerFactory = getKeyManagerFactory(keystoreLocation, keystoreFileName); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagerFactory.getKeyManagers(), null, null); + return sslContext; + } + public enum KmsProviderType { LOCAL, AWS, From 6630fe264ee689d4ce3a940e35739f2b19d65f15 Mon Sep 17 00:00:00 2001 From: Almas Abdrazak Date: Fri, 16 Jan 2026 09:17:00 -0800 Subject: [PATCH 11/60] JAVA-5988 (#1862) * JAVA-5988 * JAVA-5988 address PR comments * JAVA-5988 add a unit test to AggregatesTest * JAVA-5988 embedding fluent API with builders * JAVA-5988 introduce AbstractVectorSearchQuery * JAVA-5988 fix checkstyle * JAVA05988 use VectorSearchQuery as param * JAVA-5988 add more unit tests * JAVA-5988 add autoembedding to Scala --------- Co-authored-by: Almas Abdrazak --- .../com/mongodb/client/model/Aggregates.java | 100 +++++++- .../model/search/TextVectorSearchQuery.java | 47 ++++ .../search/TextVectorSearchQueryImpl.java | 62 +++++ .../model/search/VectorSearchQuery.java | 51 ++++ .../search/AbstractVectorSearchQuery.java | 25 ++ .../client/model/search/package-info.java | 26 ++ .../mongodb/client/model/AggregatesTest.java | 124 ++++++++-- .../mongodb/scala/model/search/package.scala | 21 ++ ...edEmbeddingVectorSearchFunctionalTest.java | 230 ++++++++++++++++++ ...utomatedEmbeddingVectorFunctionalTest.java | 28 +++ 10 files changed, 695 insertions(+), 19 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/client/model/search/TextVectorSearchQuery.java create mode 100644 driver-core/src/main/com/mongodb/client/model/search/TextVectorSearchQueryImpl.java create mode 100644 driver-core/src/main/com/mongodb/client/model/search/VectorSearchQuery.java create mode 100644 driver-core/src/main/com/mongodb/internal/client/model/search/AbstractVectorSearchQuery.java create mode 100644 driver-core/src/main/com/mongodb/internal/client/model/search/package-info.java create mode 100644 driver-sync/src/test/functional/com/mongodb/client/vector/AbstractAutomatedEmbeddingVectorSearchFunctionalTest.java create mode 100644 driver-sync/src/test/functional/com/mongodb/client/vector/AutomatedEmbeddingVectorFunctionalTest.java 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 44283ccba04..6a5950ab560 100644 --- a/driver-core/src/main/com/mongodb/client/model/Aggregates.java +++ b/driver-core/src/main/com/mongodb/client/model/Aggregates.java @@ -22,11 +22,14 @@ import com.mongodb.client.model.fill.FillOptions; import com.mongodb.client.model.fill.FillOutputField; import com.mongodb.client.model.geojson.Point; +import com.mongodb.internal.client.model.search.AbstractVectorSearchQuery; import com.mongodb.client.model.search.FieldSearchPath; import com.mongodb.client.model.search.SearchCollector; import com.mongodb.client.model.search.SearchOperator; import com.mongodb.client.model.search.SearchOptions; +import com.mongodb.client.model.search.TextVectorSearchQuery; import com.mongodb.client.model.search.VectorSearchOptions; +import com.mongodb.client.model.search.VectorSearchQuery; import com.mongodb.lang.Nullable; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -38,6 +41,8 @@ import org.bson.BsonValue; import org.bson.Document; import org.bson.BinaryVector; +import org.bson.annotations.Beta; +import org.bson.annotations.Reason; import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; @@ -65,6 +70,9 @@ @SuppressWarnings("overloads") public final class Aggregates { + private Aggregates() { + } + /** * Creates an $addFields pipeline stage * @@ -967,6 +975,41 @@ public static Bson vectorSearch( return new VectorSearchBson(path, queryVector, index, limit, options); } + /** + * Creates a {@code $vectorSearch} pipeline stage supported by MongoDB Atlas with automated embedding. + * 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. + *

+ * This overload is used for auto-embedding in Atlas. The server will automatically generate embeddings + * for the query using the model specified in the index definition or via {@link TextVectorSearchQuery#model(String)}. + *

+ * + * @param path The field to be searched. + * @param query The query specification, typically created via {@link VectorSearchQuery#textQuery(String)}. + * @param index The name of the index to use. + * @param limit The limit on the number of documents produced by the pipeline stage. + * @param options Optional {@code $vectorSearch} pipeline stage fields. + * @return The {@code $vectorSearch} pipeline stage. + * + * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch + * @mongodb.atlas.manual atlas-search/scoring/ Scoring + * @mongodb.server.release 6.0.11 + * @since 5.7.0 + */ + @Beta(Reason.SERVER) + public static Bson vectorSearch( + final FieldSearchPath path, + final VectorSearchQuery query, + final String index, + final long limit, + final VectorSearchOptions options) { + notNull("path", path); + notNull("query", query); + notNull("index", index); + notNull("options", options); + return new VectorSearchQueryBson(path, query, index, limit, options); + } + /** * Creates a {@code $vectorSearch} pipeline stage supported by MongoDB Atlas. * You may use the {@code $meta: "vectorSearchScore"} expression, e.g., via {@link Projections#metaVectorSearchScore(String)}, @@ -2155,6 +2198,60 @@ public String toString() { } } + + /** + * Same as {@link Aggregates.VectorSearchBson} but uses a query expression instead of a query vector. + */ + private static class VectorSearchQueryBson implements Bson { + private final FieldSearchPath path; + private final VectorSearchQuery query; + private final String index; + private final long limit; + private final VectorSearchOptions options; + + /** + * Given model name must be compatible with the one in the index definition. + */ + private final String embeddingModelName; + + VectorSearchQueryBson(final FieldSearchPath path, final VectorSearchQuery query, + final String index, final long limit, + final VectorSearchOptions options) { + this.path = path; + this.query = query; + this.index = index; + this.limit = limit; + this.options = options; + // when null then model name from the index definition will be used by the server + this.embeddingModelName = ((AbstractVectorSearchQuery) query).getModel(); + } + + @Override + public BsonDocument toBsonDocument(final Class documentClass, final CodecRegistry codecRegistry) { + Document specificationDoc = new Document("path", path.toValue()) + .append("query", query) + .append("index", index) + .append("limit", limit); + if (embeddingModelName != null) { + specificationDoc.append("model", embeddingModelName); + } + specificationDoc.putAll(options.toBsonDocument(documentClass, codecRegistry)); + return new Document("$vectorSearch", specificationDoc).toBsonDocument(documentClass, codecRegistry); + } + + @Override + public String toString() { + return "Stage{name=$vectorSearch" + + ", path=" + path + + ", query=" + query + + ", index=" + index + + ", limit=" + limit + + ", model=" + embeddingModelName + + ", options=" + options + + '}'; + } + } + private static class VectorSearchBson implements Bson { private final FieldSearchPath path; private final Object queryVector; @@ -2193,7 +2290,4 @@ public String toString() { + '}'; } } - - private Aggregates() { - } } diff --git a/driver-core/src/main/com/mongodb/client/model/search/TextVectorSearchQuery.java b/driver-core/src/main/com/mongodb/client/model/search/TextVectorSearchQuery.java new file mode 100644 index 00000000000..cb3c2f7803d --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/search/TextVectorSearchQuery.java @@ -0,0 +1,47 @@ +/* + * 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.annotations.Beta; +import com.mongodb.annotations.Reason; +import com.mongodb.annotations.Sealed; + +/** + * A text-based vector search query for MongoDB Atlas auto-embedding. + *

+ * This interface extends {@link VectorSearchQuery} and provides methods for configuring + * text-based queries that will be automatically embedded by the server. + *

+ * + * @see VectorSearchQuery#textQuery(String) + * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch + * @since 5.7.0 + */ +@Sealed +@Beta(Reason.SERVER) +public interface TextVectorSearchQuery extends VectorSearchQuery { + /** + * Specifies the embedding model to use for generating embeddings from the query text. + *

+ * If not specified, the model configured in the vector search index definition will be used. + * The specified model must be compatible with the model used in the index definition. + *

+ * + * @param modelName The name of the embedding model to use (e.g., "voyage-4-large"). + * @return A new {@link TextVectorSearchQuery} with the specified model. + */ + TextVectorSearchQuery model(String modelName); +} diff --git a/driver-core/src/main/com/mongodb/client/model/search/TextVectorSearchQueryImpl.java b/driver-core/src/main/com/mongodb/client/model/search/TextVectorSearchQueryImpl.java new file mode 100644 index 00000000000..3bc75a1e853 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/search/TextVectorSearchQueryImpl.java @@ -0,0 +1,62 @@ +/* + * 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.internal.client.model.search.AbstractVectorSearchQuery; +import com.mongodb.lang.Nullable; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistry; + +import static com.mongodb.assertions.Assertions.notNull; + +/** + * Package-private implementation of {@link TextVectorSearchQuery}. + */ +final class TextVectorSearchQueryImpl extends AbstractVectorSearchQuery implements TextVectorSearchQuery { + private final String text; + @Nullable + private final String model; + + TextVectorSearchQueryImpl(final String text, @Nullable final String model) { + this.text = notNull("text", text); + this.model = model; + } + + @Override + public TextVectorSearchQuery model(final String modelName) { + return new TextVectorSearchQueryImpl(text, notNull("modelName", modelName)); + } + + @Override + @Nullable + public String getModel() { + return model; + } + + @Override + public BsonDocument toBsonDocument(final Class documentClass, final CodecRegistry codecRegistry) { + return new Document("text", text).toBsonDocument(documentClass, codecRegistry); + } + + @Override + public String toString() { + return "TextVectorSearchQuery{" + + "text='" + text + '\'' + + ", model=" + (model != null ? "'" + model + '\'' : "null") + + '}'; + } +} diff --git a/driver-core/src/main/com/mongodb/client/model/search/VectorSearchQuery.java b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchQuery.java new file mode 100644 index 00000000000..b02e4ab91d1 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchQuery.java @@ -0,0 +1,51 @@ +/* + * 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.annotations.Beta; +import com.mongodb.annotations.Reason; +import com.mongodb.annotations.Sealed; +import org.bson.conversions.Bson; + +import static com.mongodb.assertions.Assertions.notNull; + +/** + * A query specification for MongoDB Atlas vector search with automated embedding. + *

+ * This interface provides factory methods for creating type-safe query objects that can be used + * with the {@code $vectorSearch} aggregation pipeline stage for auto-embedding functionality. + *

+ * + * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch + * @since 5.7.0 + */ +@Sealed +@Beta(Reason.SERVER) +public interface VectorSearchQuery extends Bson { + /** + * Creates a text-based vector search query that will be automatically embedded by the server. + *

+ * The server will generate embeddings for the provided text using the model specified in the + * vector search index definition, or an explicitly specified model via {@link TextVectorSearchQuery#model(String)}. + *

+ * + * @param text The text to be embedded and searched. + * @return A {@link TextVectorSearchQuery} that can be further configured. + */ + static TextVectorSearchQuery textQuery(final String text) { + return new TextVectorSearchQueryImpl(notNull("text", text), null); + } +} diff --git a/driver-core/src/main/com/mongodb/internal/client/model/search/AbstractVectorSearchQuery.java b/driver-core/src/main/com/mongodb/internal/client/model/search/AbstractVectorSearchQuery.java new file mode 100644 index 00000000000..6708905209d --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/client/model/search/AbstractVectorSearchQuery.java @@ -0,0 +1,25 @@ +/* + * 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.internal.client.model.search; + +import com.mongodb.client.model.search.VectorSearchQuery; + +public abstract class AbstractVectorSearchQuery implements VectorSearchQuery { + + public abstract String getModel(); + +} diff --git a/driver-core/src/main/com/mongodb/internal/client/model/search/package-info.java b/driver-core/src/main/com/mongodb/internal/client/model/search/package-info.java new file mode 100644 index 00000000000..e8c06ef4394 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/client/model/search/package-info.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * This package contains internal functionality that may change at any time. + */ + +@Internal +@NonNullApi +package com.mongodb.internal.client.model.search; + +import com.mongodb.annotations.Internal; +import com.mongodb.lang.NonNullApi; diff --git a/driver-core/src/test/functional/com/mongodb/client/model/AggregatesTest.java b/driver-core/src/test/functional/com/mongodb/client/model/AggregatesTest.java index 2b1ad7d5b4b..7fd01712ea3 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/AggregatesTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/AggregatesTest.java @@ -19,6 +19,9 @@ import com.mongodb.client.model.geojson.Point; import com.mongodb.client.model.geojson.Position; import com.mongodb.client.model.mql.MqlValues; + +import static com.mongodb.client.model.search.VectorSearchOptions.exactVectorSearchOptions; + import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.Document; @@ -41,10 +44,14 @@ import static com.mongodb.client.model.Aggregates.geoNear; import static com.mongodb.client.model.Aggregates.group; import static com.mongodb.client.model.Aggregates.unset; +import static com.mongodb.client.model.Aggregates.vectorSearch; import static com.mongodb.client.model.GeoNearOptions.geoNearOptions; import static com.mongodb.client.model.Sorts.ascending; import static com.mongodb.client.model.Windows.Bound.UNBOUNDED; import static com.mongodb.client.model.Windows.documents; +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.VectorSearchQuery.textQuery; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; @@ -64,8 +71,8 @@ private static Stream groupWithQuantileSource() { @ParameterizedTest @MethodSource("groupWithQuantileSource") public void shouldGroupWithQuantile(final BsonField quantileAccumulator, - final Object expectedGroup1, - final Object expectedGroup2) { + final Object expectedGroup1, + final Object expectedGroup2) { //given assumeTrue(serverVersionAtLeast(7, 0)); getCollectionHelper().insertDocuments("[\n" @@ -116,8 +123,8 @@ private static Stream setWindowFieldWithQuantileSource() { @ParameterizedTest @MethodSource("setWindowFieldWithQuantileSource") public void shouldSetWindowFieldWithQuantile(@Nullable final Object partitionBy, - final WindowOutputField output, - final List expectedFieldValues) { + final WindowOutputField output, + final List expectedFieldValues) { //given assumeTrue(serverVersionAtLeast(7, 0)); Document[] original = new Document[]{ @@ -199,18 +206,18 @@ public void testGeoNear() { )); List pipeline = assertPipeline("{\n" - + " $geoNear: {\n" - + " near: { type: 'Point', coordinates: [ -73.99279 , 40.719296 ] },\n" - + " distanceField: 'dist.calculated',\n" - + " minDistance: 0,\n" - + " maxDistance: 2,\n" - + " query: { category: 'Parks' },\n" - + " includeLocs: 'dist.location',\n" - + " spherical: true,\n" - + " key: 'location',\n" - + " distanceMultiplier: 10.0\n" - + " }\n" - + "}", + + " $geoNear: {\n" + + " near: { type: 'Point', coordinates: [ -73.99279 , 40.719296 ] },\n" + + " distanceField: 'dist.calculated',\n" + + " minDistance: 0,\n" + + " maxDistance: 2,\n" + + " query: { category: 'Parks' },\n" + + " includeLocs: 'dist.location',\n" + + " spherical: true,\n" + + " key: 'location',\n" + + " distanceMultiplier: 10.0\n" + + " }\n" + + "}", geoNear( new Point(new Position(-73.99279, 40.719296)), "dist.calculated", @@ -282,4 +289,89 @@ public void testDocumentsLookup() { parseToList("[{_id:1, a:8, added: [{a: 5}]}, {_id:2, a:9, added: [{a: 5}]}]"), getCollectionHelper().aggregate(Arrays.asList(lookupStage))); } + + @Test + public void testAprVectorSearchWithQueryObject() { + assertPipeline( + "{" + + " $vectorSearch: {" + + " path: 'plot'," + + " query: {text: 'movies about love'}," + + " index: 'test_index'," + + " limit: {$numberLong: '5'}," + + " numCandidates: {$numberLong: '5'}" + + " }" + + "}", + vectorSearch( + fieldPath("plot"), + textQuery("movies about love"), + "test_index", + 5L, + approximateVectorSearchOptions(5L) + )); + } + + @Test + public void testAprVectorSearchWithQueryObjectAndEmbeddingModel() { + assertPipeline( + "{" + + " $vectorSearch: {" + + " path: 'plot'," + + " query: {text: 'movies about love'}," + + " index: 'test_index'," + + " limit: {$numberLong: '5'}," + + " model: 'voyage-4-large'," + + " numCandidates: {$numberLong: '5'}" + + " }" + + "}", + vectorSearch( + fieldPath("plot"), + textQuery("movies about love").model("voyage-4-large"), + "test_index", + 5L, + approximateVectorSearchOptions(5L) + )); + } + + @Test + public void testExactVectorSearchWithQueryObjectAndEmbeddingModel() { + assertPipeline( + "{" + + " $vectorSearch: {" + + " path: 'plot'," + + " query: {text: 'movies about love'}," + + " index: 'test_index'," + + " limit: {$numberLong: '5'}," + + " model: 'voyage-4-large'," + + " exact: true" + + " }" + + "}", + vectorSearch( + fieldPath("plot"), + textQuery("movies about love").model("voyage-4-large"), + "test_index", + 5L, + exactVectorSearchOptions() + )); + } + @Test + public void testExactVectorSearchWithQueryObject() { + assertPipeline( + "{" + + " $vectorSearch: {" + + " path: 'plot'," + + " query: {text: 'movies about love'}," + + " index: 'test_index'," + + " limit: {$numberLong: '5'}," + + " exact: true" + + " }" + + "}", + vectorSearch( + fieldPath("plot"), + textQuery("movies about love"), + "test_index", + 5L, + exactVectorSearchOptions() + )); + } } diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala index 771e800801d..baa454b1ee7 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala @@ -271,6 +271,27 @@ package object search { @Beta(Array(Reason.CLIENT)) type SearchOptions = com.mongodb.client.model.search.SearchOptions + /** + * A query specification for MongoDB Atlas vector search with automated embedding. + * + * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] + * @since 5.7 + */ + @Sealed + @Beta(Array(Reason.SERVER)) + type VectorSearchQuery = com.mongodb.client.model.search.VectorSearchQuery + + /** + * A text-based vector search query for MongoDB Atlas auto-embedding. + * + * @see `VectorSearchQuery.textQuery(String)` + * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] + * @since 5.7 + */ + @Sealed + @Beta(Array(Reason.SERVER)) + type TextVectorSearchQuery = com.mongodb.client.model.search.TextVectorSearchQuery + /** * Represents optional fields of the `\$vectorSearch` pipeline stage of an aggregation pipeline. * diff --git a/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractAutomatedEmbeddingVectorSearchFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractAutomatedEmbeddingVectorSearchFunctionalTest.java new file mode 100644 index 00000000000..0331ed563c9 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractAutomatedEmbeddingVectorSearchFunctionalTest.java @@ -0,0 +1,230 @@ +/* + * 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.MongoCommandException; +import com.mongodb.client.Fixture; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.OperationTest; +import com.mongodb.client.model.SearchIndexModel; +import com.mongodb.client.model.SearchIndexType; +import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.PojoCodecProvider; +import org.bson.conversions.Bson; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry; +import static com.mongodb.client.model.Aggregates.vectorSearch; +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.VectorSearchQuery.textQuery; +import static java.util.Arrays.asList; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; +import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; + +/** + * The test cases were borrowed from + * this repository. + */ +public abstract class AbstractAutomatedEmbeddingVectorSearchFunctionalTest extends OperationTest { + + private static final String FIELD_SEARCH_PATH = "plot"; + // as of 2025-01-13 only voyage 4 is supported for automated embedding + // it might change in the future so for now we are only testing with voyage-4-large model + private static final String INDEX_NAME = "voyage_4"; + + private static final String MOVIE_NAME = "Breathe"; + private static final CodecRegistry CODEC_REGISTRY = fromRegistries(getDefaultCodecRegistry(), + fromProviders(PojoCodecProvider + .builder() + .automatic(true).build())); + private MongoCollection documentCollection; + + private MongoClient mongoClient; + + @BeforeEach + public void setUp() { + //TODO-JAVA-6059 remove this line when Atlas Vector Search with automated embedding is generally available + // right now atlas search with automated embedding is in private preview and + // only available via a custom docker image + Assumptions.assumeTrue(false); + + 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(); + } + + protected abstract MongoClient getMongoClient(MongoClientSettings settings); + + /** + * Happy path for automated embedding with Voyage-4 model. + * + *

Steps: + *

    + *
  1. Create empty collection
  2. + *
  3. Create auto-embedding search index with voyage-4-large model
  4. + *
  5. Insert movie documents
  6. + *
  7. Run vector search query using query text
  8. + *
+ * + *

Expected: Query returns "Breathe" as the top match for "movies about love" + */ + @Test + @DisplayName("should create auto embedding index and run vector search query using query text") + void shouldCreateAutoEmbeddingIndexAndRunVectorSearchQuery() throws InterruptedException { + mongoClient.getDatabase(getDatabaseName()).createCollection(getCollectionName()); + createAutoEmbeddingIndex("voyage-4-large"); + // TODO-JAVA-6063 + // community search with automated embedding doesn't support queryable field yet + // once supported remove the sleep and uncomment waitForIndex + TimeUnit.SECONDS.sleep(2L); + //waitForIndex(documentCollection, INDEX_NAME); + insertDocumentsForEmbedding(); + // TODO-JAVA-6063 wait for embeddings to be generated + // once there is an official way to check the index status, we should use it instead of sleep + // there is a workaround to pass a feature flag `internalListAllIndexesForTesting` but it's not official yet + TimeUnit.SECONDS.sleep(2L); + runEmbeddingQuery(); + } + + @Test + @DisplayName("should fail when invalid model name was used") + void shouldFailWhenInvalidModelNameWasUsed() { + mongoClient.getDatabase(getDatabaseName()).createCollection(getCollectionName()); + Assertions.assertThrows( + MongoCommandException.class, + () -> createAutoEmbeddingIndex("test"), + "Valid voyage model name was not used" + ); + } + + @Test + @DisplayName("should fail to create auto embedding index without model") + void shouldFailToCreateAutoEmbeddingIndexWithoutModel() { + mongoClient.getDatabase(getDatabaseName()).createCollection(getCollectionName()); + SearchIndexModel indexModel = new SearchIndexModel( + INDEX_NAME, + new Document( + "fields", + Collections.singletonList( + new Document("type", "autoEmbed") + .append("modality", "text") + .append("path", FIELD_SEARCH_PATH) + )), + SearchIndexType.vectorSearch() + ); + Assertions.assertThrows( + MongoCommandException.class, + () -> documentCollection.createSearchIndexes(Collections.singletonList(indexModel)), + "Expected index creation to fail because model is not specified" + ); + } + + private void runEmbeddingQuery() { + List pipeline = asList( + vectorSearch( + fieldPath(FIELD_SEARCH_PATH), + textQuery("movies about love"), + INDEX_NAME, + 5L, // limit + approximateVectorSearchOptions(5L) // numCandidates + ) + ); + final List documents = documentCollection.aggregate(pipeline).into(new ArrayList<>()); + + Assertions.assertFalse(documents.isEmpty(), "Expected to get some results from vector search query"); + Assertions.assertEquals(MOVIE_NAME, documents.get(0).getString("title")); + } + + /** + * All the documents were borrowed from + * here + */ + private void insertDocumentsForEmbedding() { + documentCollection.insertMany(asList( + new Document() + .append("cast", asList("Cillian Murphy", "Emily Blunt", "Matt Damon")) + .append("director", "Christopher Nolan") + .append("genres", asList("Biography", "Drama", "History")) + .append("imdb", new Document() + .append("rating", 8.3) + .append("votes", 680000)) + .append("plot", "The story of American scientist J. Robert Oppenheimer and his role in the development of the atomic bomb during World War II.") + .append("runtime", 180) + .append("title", "Oppenheimer") + .append("year", 2023), + new Document() + .append("cast", asList("Andrew Garfield", "Claire Foy", "Hugh Bonneville")) + .append("director", "Andy Serkis") + .append("genres", asList("Biography", "Drama", "Romance")) + .append("imdb", new Document() + .append("rating", 7.2) + .append("votes", 42000)) + .append("plot", "The inspiring true love story of Robin and Diana Cavendish, an adventurous couple who refuse to give up in the face of a devastating disease.") + .append("runtime", 118) + .append("title", MOVIE_NAME) + .append("year", 2017) + )); + } + + private void createAutoEmbeddingIndex(final String modelName) { + SearchIndexModel indexModel = new SearchIndexModel( + INDEX_NAME, + new Document( + "fields", + Collections.singletonList( + new Document("type", "autoEmbed") // type autoEmbed accepts a text + .append("modality", "text") + .append("model", modelName) + .append("path", FIELD_SEARCH_PATH) + )), + SearchIndexType.vectorSearch() + ); + List result = documentCollection.createSearchIndexes(Collections.singletonList(indexModel)); + + Assertions.assertFalse(result.isEmpty()); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/vector/AutomatedEmbeddingVectorFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/vector/AutomatedEmbeddingVectorFunctionalTest.java new file mode 100644 index 00000000000..8f7db557440 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/vector/AutomatedEmbeddingVectorFunctionalTest.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 AutomatedEmbeddingVectorFunctionalTest extends AbstractAutomatedEmbeddingVectorSearchFunctionalTest { + @Override + protected MongoClient getMongoClient(final MongoClientSettings settings) { + return MongoClients.create(settings); + } +} From 2a26a16390fac8c56a833a08b2165e7b07ab7869 Mon Sep 17 00:00:00 2001 From: Almas Abdrazak <20584185+strogiyotec@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:27:20 +0000 Subject: [PATCH 12/60] Version: bump 5.7.0-beta0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 00024442054..3c6c3297aaa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ # limitations under the License. # -version=5.7.0-SNAPSHOT +version=5.7.0-beta0 org.gradle.daemon=true org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en From e2f5c40cecf41b54c4a5957e953c94227f34713f Mon Sep 17 00:00:00 2001 From: Almas Abdrazak <20584185+strogiyotec@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:27:20 +0000 Subject: [PATCH 13/60] Version: bump 5.7.0-SNAPSHOT --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3c6c3297aaa..00024442054 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ # limitations under the License. # -version=5.7.0-beta0 +version=5.7.0-SNAPSHOT org.gradle.daemon=true org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en From 1ed116f48ab2812cc95073240f23dfe61b843bed Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 3 Dec 2025 17:39:43 +0000 Subject: [PATCH 14/60] JAVA-5950 - Update Transactions Convenient API with exponential backoff on retries --- .../mongodb/internal/ExponentialBackoff.java | 174 +++++++++++++++ .../internal/ExponentialBackoffTest.java | 205 ++++++++++++++++++ .../client/internal/ClientSessionImpl.java | 38 +++- .../client/WithTransactionProseTest.java | 39 ++++ 4 files changed, 454 insertions(+), 2 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java create mode 100644 driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java diff --git a/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java new file mode 100644 index 00000000000..518286319ad --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java @@ -0,0 +1,174 @@ +/* + * 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.internal; + +import com.mongodb.annotations.NotThreadSafe; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * Implements exponential backoff with jitter for retry scenarios. + * Formula: delayMS = jitter * min(maxBackoffMs, baseBackoffMs * growthFactor^retryCount) + * where jitter is random value [0, 1). + * + *

This class provides factory methods for common use cases: + *

    + *
  • {@link #forTransactionRetry()} - For withTransaction retries (5ms base, 500ms max, 1.5 growth)
  • + *
  • {@link #forCommandRetry()} - For command retries with overload (100ms base, 10000ms max, 2.0 growth)
  • + *
+ */ +@NotThreadSafe +public final class ExponentialBackoff { + // Transaction retry constants (per spec) + private static final double TRANSACTION_BASE_BACKOFF_MS = 5.0; + private static final double TRANSACTION_MAX_BACKOFF_MS = 500.0; + private static final double TRANSACTION_BACKOFF_GROWTH = 1.5; + + // Command retry constants (per spec) + private static final double COMMAND_BASE_BACKOFF_MS = 100.0; + private static final double COMMAND_MAX_BACKOFF_MS = 10000.0; + private static final double COMMAND_BACKOFF_GROWTH = 2.0; + + private final double baseBackoffMs; + private final double maxBackoffMs; + private final double growthFactor; + private int retryCount = 0; + + /** + * Creates an exponential backoff instance with specified parameters. + * + * @param baseBackoffMs Initial backoff in milliseconds + * @param maxBackoffMs Maximum backoff cap in milliseconds + * @param growthFactor Exponential growth factor (e.g., 1.5 or 2.0) + */ + public ExponentialBackoff(final double baseBackoffMs, final double maxBackoffMs, final double growthFactor) { + this.baseBackoffMs = baseBackoffMs; + this.maxBackoffMs = maxBackoffMs; + this.growthFactor = growthFactor; + } + + /** + * Creates a backoff instance configured for withTransaction retries. + * Uses: 5ms base, 500ms max, 1.5 growth factor. + * + * @return ExponentialBackoff configured for transaction retries + */ + public static ExponentialBackoff forTransactionRetry() { + return new ExponentialBackoff( + TRANSACTION_BASE_BACKOFF_MS, + TRANSACTION_MAX_BACKOFF_MS, + TRANSACTION_BACKOFF_GROWTH + ); + } + + /** + * Creates a backoff instance configured for command retries during overload. + * Uses: 100ms base, 10000ms max, 2.0 growth factor. + * + * @return ExponentialBackoff configured for command retries + */ + public static ExponentialBackoff forCommandRetry() { + return new ExponentialBackoff( + COMMAND_BASE_BACKOFF_MS, + COMMAND_MAX_BACKOFF_MS, + COMMAND_BACKOFF_GROWTH + ); + } + + /** + * Calculate next backoff delay with jitter. + * + * @return delay in milliseconds + */ + public long calculateDelayMs() { + double jitter = ThreadLocalRandom.current().nextDouble(); + double exponentialBackoff = baseBackoffMs * Math.pow(growthFactor, retryCount); + double cappedBackoff = Math.min(exponentialBackoff, maxBackoffMs); + retryCount++; + return Math.round(jitter * cappedBackoff); + } + + /** + * Apply backoff delay by sleeping current thread. + * + * @throws InterruptedException if thread is interrupted during sleep + */ + public void applyBackoff() throws InterruptedException { + long delayMs = calculateDelayMs(); + if (delayMs > 0) { + Thread.sleep(delayMs); + } + } + + /** + * Check if applying backoff would exceed the retry time limit. + * @param startTimeMs start time of retry attempts + * @param maxRetryTimeMs maximum retry time allowed + * @return true if backoff would exceed limit, false otherwise + */ +// public boolean wouldExceedTimeLimit(final long startTimeMs, final long maxRetryTimeMs) { +// long elapsedMs = ClientSessionClock.INSTANCE.now() - startTimeMs; +// // Peek at next delay without incrementing counter +// double exponentialBackoff = baseBackoffMs * Math.pow(growthFactor, retryCount); +// double cappedBackoff = Math.min(exponentialBackoff, maxBackoffMs); +// long maxPossibleDelay = Math.round(cappedBackoff); // worst case with jitter=1 +// return elapsedMs + maxPossibleDelay > maxRetryTimeMs; +// } + + /** + * Reset retry counter for new sequence of retries. + */ + public void reset() { + retryCount = 0; + } + + /** + * Get current retry count for testing. + * + * @return current retry count + */ + public int getRetryCount() { + return retryCount; + } + + /** + * Get the base backoff in milliseconds. + * + * @return base backoff + */ + public double getBaseBackoffMs() { + return baseBackoffMs; + } + + /** + * Get the maximum backoff in milliseconds. + * + * @return maximum backoff + */ + public double getMaxBackoffMs() { + return maxBackoffMs; + } + + /** + * Get the growth factor. + * + * @return growth factor + */ + public double getGrowthFactor() { + return growthFactor; + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java new file mode 100644 index 00000000000..bfee96e67fb --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java @@ -0,0 +1,205 @@ +/* + * 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.internal; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ExponentialBackoffTest { + + @Test + void testTransactionRetryBackoff() { + ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); + + // Verify configuration + assertEquals(5.0, backoff.getBaseBackoffMs()); + assertEquals(500.0, backoff.getMaxBackoffMs()); + assertEquals(1.5, backoff.getGrowthFactor()); + + // First retry (i=0): delay = jitter * min(5 * 1.5^0, 500) = jitter * 5 + // Since jitter is random [0,1), the delay should be between 0 and 5ms + long delay1 = backoff.calculateDelayMs(); + assertTrue(delay1 >= 0 && delay1 <= 5, "First delay should be 0-5ms, got: " + delay1); + + // Second retry (i=1): delay = jitter * min(5 * 1.5^1, 500) = jitter * 7.5 + long delay2 = backoff.calculateDelayMs(); + assertTrue(delay2 >= 0 && delay2 <= 8, "Second delay should be 0-8ms, got: " + delay2); + + // Third retry (i=2): delay = jitter * min(5 * 1.5^2, 500) = jitter * 11.25 + long delay3 = backoff.calculateDelayMs(); + assertTrue(delay3 >= 0 && delay3 <= 12, "Third delay should be 0-12ms, got: " + delay3); + + // Verify the retry count is incrementing properly + assertEquals(3, backoff.getRetryCount()); + } + + @Test + void testTransactionRetryBackoffRespectsMaximum() { + ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); + + // Advance to a high retry count where backoff would exceed 500ms without capping + for (int i = 0; i < 20; i++) { + backoff.calculateDelayMs(); + } + + // Even at high retry counts, delay should never exceed 500ms + for (int i = 0; i < 5; i++) { + long delay = backoff.calculateDelayMs(); + assertTrue(delay >= 0 && delay <= 500, "Delay should be capped at 500ms, got: " + delay); + } + } + + @Test + void testTransactionRetryBackoffSequenceWithExpectedValues() { + // Test that the backoff sequence follows the expected pattern with growth factor 1.5 + // Expected sequence (without jitter): 5, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, ... + // With jitter, actual values will be between 0 and these maxima + + ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); + + double[] expectedMaxValues = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, + 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; + + for (int i = 0; i < expectedMaxValues.length; i++) { + long delay = backoff.calculateDelayMs(); + assertTrue(delay >= 0 && delay <= Math.round(expectedMaxValues[i]), + String.format("Retry %d: delay should be 0-%d ms, got: %d", i, Math.round(expectedMaxValues[i]), delay)); + } + } + + @Test + void testCommandRetryBackoff() { + ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); + + // Verify configuration + assertEquals(100.0, backoff.getBaseBackoffMs()); + assertEquals(10000.0, backoff.getMaxBackoffMs()); + assertEquals(2.0, backoff.getGrowthFactor()); + + // Test sequence with growth factor 2.0 + // Expected max delays: 100, 200, 400, 800, 1600, 3200, 6400, 10000 (capped) + long delay1 = backoff.calculateDelayMs(); + assertTrue(delay1 >= 0 && delay1 <= 100, "First delay should be 0-100ms, got: " + delay1); + + long delay2 = backoff.calculateDelayMs(); + assertTrue(delay2 >= 0 && delay2 <= 200, "Second delay should be 0-200ms, got: " + delay2); + + long delay3 = backoff.calculateDelayMs(); + assertTrue(delay3 >= 0 && delay3 <= 400, "Third delay should be 0-400ms, got: " + delay3); + + long delay4 = backoff.calculateDelayMs(); + assertTrue(delay4 >= 0 && delay4 <= 800, "Fourth delay should be 0-800ms, got: " + delay4); + + long delay5 = backoff.calculateDelayMs(); + assertTrue(delay5 >= 0 && delay5 <= 1600, "Fifth delay should be 0-1600ms, got: " + delay5); + } + + @Test + void testCommandRetryBackoffRespectsMaximum() { + ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); + + // Advance to where exponential would exceed 10000ms + for (int i = 0; i < 10; i++) { + backoff.calculateDelayMs(); + } + + // Even at high retry counts, delay should never exceed 10000ms + for (int i = 0; i < 5; i++) { + long delay = backoff.calculateDelayMs(); + assertTrue(delay >= 0 && delay <= 10000, "Delay should be capped at 10000ms, got: " + delay); + } + } + + @Test + void testCustomBackoff() { + // Test with custom parameters + ExponentialBackoff backoff = new ExponentialBackoff(50.0, 2000.0, 1.8); + + assertEquals(50.0, backoff.getBaseBackoffMs()); + assertEquals(2000.0, backoff.getMaxBackoffMs()); + assertEquals(1.8, backoff.getGrowthFactor()); + + // First delay: 0-50ms + long delay1 = backoff.calculateDelayMs(); + assertTrue(delay1 >= 0 && delay1 <= 50, "First delay should be 0-50ms, got: " + delay1); + + // Second delay: 0-90ms (50 * 1.8) + long delay2 = backoff.calculateDelayMs(); + assertTrue(delay2 >= 0 && delay2 <= 90, "Second delay should be 0-90ms, got: " + delay2); + } + + @Test + void testReset() { + ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); + + // Perform some retries + backoff.calculateDelayMs(); + backoff.calculateDelayMs(); + assertEquals(2, backoff.getRetryCount()); + + // Reset and verify counter is back to 0 + backoff.reset(); + assertEquals(0, backoff.getRetryCount()); + + // First delay after reset should be in the initial range again + long delay = backoff.calculateDelayMs(); + assertTrue(delay >= 0 && delay <= 5, "First delay after reset should be 0-5ms, got: " + delay); + } + +// @Test +// void testWouldExceedTimeLimitTransactionRetry() { +// ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); +// long startTime = ClientSessionClock.INSTANCE.now(); +// +// // Initially, should not exceed time limit +// assertFalse(backoff.wouldExceedTimeLimit(startTime, 120000)); +// +// // With very little time remaining (4ms), first backoff (up to 5ms) would exceed +// long nearLimitTime = startTime - 119996; // 4ms remaining +// assertTrue(backoff.wouldExceedTimeLimit(nearLimitTime, 120000)); +// } + +// @Test +// void testWouldExceedTimeLimitCommandRetry() { +// ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); +// long startTime = ClientSessionClock.INSTANCE.now(); +// +// // Initially, should not exceed time limit +// assertFalse(backoff.wouldExceedTimeLimit(startTime, 10000)); +// +// // With 99ms remaining, first backoff (up to 100ms) would exceed +// long nearLimitTime = startTime - 9901; // 99ms remaining +// assertTrue(backoff.wouldExceedTimeLimit(nearLimitTime, 10000)); +// } + + @Test + void testCommandRetrySequenceMatchesSpec() { + // Test that command retry follows spec: 100ms * 2^i capped at 10000ms + ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); + + double[] expectedMaxValues = {100.0, 200.0, 400.0, 800.0, 1600.0, 3200.0, 6400.0, 10000.0, 10000.0}; + + for (int i = 0; i < expectedMaxValues.length; i++) { + long delay = backoff.calculateDelayMs(); + double expectedMax = expectedMaxValues[i]; + assertTrue(delay >= 0 && delay <= Math.round(expectedMax), + String.format("Retry %d: delay should be 0-%d ms, got: %d", i, Math.round(expectedMax), delay)); + } + } +} diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index aa1414dce5d..63e1d68165b 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -22,12 +22,14 @@ import com.mongodb.MongoExecutionTimeoutException; import com.mongodb.MongoInternalException; import com.mongodb.MongoOperationTimeoutException; +import com.mongodb.MongoTimeoutException; import com.mongodb.ReadConcern; import com.mongodb.TransactionOptions; import com.mongodb.WriteConcern; import com.mongodb.client.ClientSession; import com.mongodb.client.TransactionBody; import com.mongodb.internal.TimeoutContext; +import com.mongodb.internal.ExponentialBackoff; import com.mongodb.internal.operation.AbortTransactionOperation; import com.mongodb.internal.operation.CommitTransactionOperation; import com.mongodb.internal.operation.OperationHelper; @@ -251,10 +253,35 @@ public T withTransaction(final TransactionBody transactionBody, final Tra notNull("transactionBody", transactionBody); long startTime = ClientSessionClock.INSTANCE.now(); TimeoutContext withTransactionTimeoutContext = createTimeoutContext(options); + // Use CSOT timeout if set, otherwise default to MAX_RETRY_TIME_LIMIT_MS + Long timeoutMS = withTransactionTimeoutContext.getTimeoutSettings().getTimeoutMS(); + long maxRetryTimeMS = timeoutMS != null ? timeoutMS : MAX_RETRY_TIME_LIMIT_MS; + ExponentialBackoff transactionBackoff = null; + boolean isRetry = false; try { outer: while (true) { + // Apply exponential backoff before retrying transaction + if (isRetry) { + // Check if we've exceeded the retry time limit BEFORE applying backoff + if (ClientSessionClock.INSTANCE.now() - startTime >= maxRetryTimeMS) { + throw withTransactionTimeoutContext.hasTimeoutMS() + ? new MongoOperationTimeoutException("Transaction retry exceeded the timeout limit") + : new MongoTimeoutException("Transaction retry time limit exceeded"); + + } + if (transactionBackoff == null) { + transactionBackoff = ExponentialBackoff.forTransactionRetry(); + } + try { + transactionBackoff.applyBackoff(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new MongoClientException("Transaction retry interrupted", e); + } + } + isRetry = true; T retVal; try { startTransaction(options, withTransactionTimeoutContext.copyTimeoutContext()); @@ -269,7 +296,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra if (e instanceof MongoException && !(e instanceof MongoOperationTimeoutException)) { MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e); if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL) - && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { + && ClientSessionClock.INSTANCE.now() - startTime < maxRetryTimeMS) { if (transactionSpan != null) { transactionSpan.spanFinalizing(false); } @@ -280,13 +307,20 @@ public T withTransaction(final TransactionBody transactionBody, final Tra } if (transactionState == TransactionState.IN) { while (true) { + // Check if we've exceeded the retry time limit + if (ClientSessionClock.INSTANCE.now() - startTime >= maxRetryTimeMS) { + throw hasTimeoutMS(withTransactionTimeoutContext) + ? new MongoOperationTimeoutException("Transaction commit retry time limit exceeded") + : new MongoTimeoutException("Transaction commit retry time limit exceeded"); + } + try { commitTransaction(false); break; } catch (MongoException e) { clearTransactionContextOnError(e); if (!(e instanceof MongoOperationTimeoutException) - && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { + && ClientSessionClock.INSTANCE.now() - startTime < maxRetryTimeMS) { applyMajorityWriteConcernToTransactionOptions(); if (!(e instanceof MongoExecutionTimeoutException) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 1afbf61565e..406cee6c9a6 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import static com.mongodb.ClusterFixture.TIMEOUT; import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet; @@ -203,6 +204,44 @@ public void testTimeoutMSAndLegacySettings() { } } + // + // Test that exponential backoff is applied when retrying transactions + // Backoff uses growth factor of 1.5 as per spec + // + @Test + public void testExponentialBackoffOnTransientError() { + // Configure failpoint to simulate transient errors + MongoDatabase failPointAdminDb = client.getDatabase("admin"); + failPointAdminDb.runCommand( + Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 3}, " + + "'data': {'failCommands': ['insert'], 'errorCode': 112, " + + "'errorLabels': ['TransientTransactionError']}}")); + + try (ClientSession session = client.startSession()) { + long startTime = System.currentTimeMillis(); + + // Track retry count + AtomicInteger retryCount = new AtomicInteger(0); + + session.withTransaction(() -> { + retryCount.incrementAndGet(); // Count the attempt before the operation that might fail + collection.insertOne(session, Document.parse("{ _id : 'backoff-test' }")); + return retryCount; + }); + + long elapsedTime = System.currentTimeMillis() - startTime; + + // With backoff (growth factor 1.5), we expect at least some delay between retries + // Expected delays (without jitter): 5ms, 7.5ms, 11.25ms + // With jitter, actual delays will be between 0 and these values + // 3 retries with backoff should take at least a few milliseconds + assertTrue(elapsedTime > 5, "Expected backoff delays to be applied"); + assertEquals(4, retryCount.get(), "Expected 1 initial attempt + 3 retries"); + } finally { + failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); + } + } + private boolean canRunTests() { return isSharded() || isDiscoverableReplicaSet(); } From eb8b4ad3b69a4d8f1139bc0d4fb55f08ddc20314 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 13:27:38 +0000 Subject: [PATCH 15/60] Simplifying test, clean up. --- .../com/mongodb/client/internal/ClientSessionImpl.java | 1 - .../com/mongodb/client/WithTransactionProseTest.java | 7 ------- 2 files changed, 8 deletions(-) diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index 63e1d68165b..855c21e275e 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -269,7 +269,6 @@ public T withTransaction(final TransactionBody transactionBody, final Tra throw withTransactionTimeoutContext.hasTimeoutMS() ? new MongoOperationTimeoutException("Transaction retry exceeded the timeout limit") : new MongoTimeoutException("Transaction retry time limit exceeded"); - } if (transactionBackoff == null) { transactionBackoff = ExponentialBackoff.forTransactionRetry(); diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 406cee6c9a6..01768a43671 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -229,13 +229,6 @@ public void testExponentialBackoffOnTransientError() { return retryCount; }); - long elapsedTime = System.currentTimeMillis() - startTime; - - // With backoff (growth factor 1.5), we expect at least some delay between retries - // Expected delays (without jitter): 5ms, 7.5ms, 11.25ms - // With jitter, actual delays will be between 0 and these values - // 3 retries with backoff should take at least a few milliseconds - assertTrue(elapsedTime > 5, "Expected backoff delays to be applied"); assertEquals(4, retryCount.get(), "Expected 1 initial attempt + 3 retries"); } finally { failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); From b8b0e1a40a78134f56171ab20ff2e5c52cc40055 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 14:22:57 +0000 Subject: [PATCH 16/60] Fixing test --- .../com/mongodb/client/internal/ClientSessionImpl.java | 7 ------- .../com/mongodb/client/WithTransactionProseTest.java | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index 855c21e275e..b38b1f9027d 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -306,13 +306,6 @@ public T withTransaction(final TransactionBody transactionBody, final Tra } if (transactionState == TransactionState.IN) { while (true) { - // Check if we've exceeded the retry time limit - if (ClientSessionClock.INSTANCE.now() - startTime >= maxRetryTimeMS) { - throw hasTimeoutMS(withTransactionTimeoutContext) - ? new MongoOperationTimeoutException("Transaction commit retry time limit exceeded") - : new MongoTimeoutException("Transaction commit retry time limit exceeded"); - } - try { commitTransaction(false); break; diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 01768a43671..c8a27cce9cc 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -147,7 +147,7 @@ public void testRetryTimeoutEnforcedUnknownTransactionCommit() { public void testRetryTimeoutEnforcedTransientTransactionErrorOnCommit() { MongoDatabase failPointAdminDb = client.getDatabase("admin"); failPointAdminDb.runCommand( - Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 2}, " + Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 1}, " + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251, 'codeName': 'NoSuchTransaction', " + "'errmsg': 'Transaction 0 has been aborted', 'closeConnection': false}}")); From c05ce05db6e95c2be4fef2aba39b0891c3856e50 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 15:32:31 +0000 Subject: [PATCH 17/60] Update driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../main/com/mongodb/client/internal/ClientSessionImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index b38b1f9027d..a65d3340886 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -267,8 +267,8 @@ public T withTransaction(final TransactionBody transactionBody, final Tra // Check if we've exceeded the retry time limit BEFORE applying backoff if (ClientSessionClock.INSTANCE.now() - startTime >= maxRetryTimeMS) { throw withTransactionTimeoutContext.hasTimeoutMS() - ? new MongoOperationTimeoutException("Transaction retry exceeded the timeout limit") - : new MongoTimeoutException("Transaction retry time limit exceeded"); + ? new MongoOperationTimeoutException("Transaction retry time limit of " + maxRetryTimeMS + "ms exceeded") + : new MongoTimeoutException("Transaction retry time limit of " + maxRetryTimeMS + "ms exceeded"); } if (transactionBackoff == null) { transactionBackoff = ExponentialBackoff.forTransactionRetry(); From aa966590a81f1d5bf83358c0062bc065f747b0b9 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 15:43:23 +0000 Subject: [PATCH 18/60] retrigger checks From 98fc57bb74a41e18dd9da674ca779dcc5ab1da0e Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 17:01:23 +0000 Subject: [PATCH 19/60] retrigger checks From f98262e2eda0fa421927d664e32d738184ef30cb Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 17:16:33 +0000 Subject: [PATCH 20/60] retrigger checks From bfc89fcc560d293fa3be09fe47e68010111a7e85 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 17:20:32 +0000 Subject: [PATCH 21/60] retrigger checks From d9405ef4e48b7eed082d349b5026ec14afbba479 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 17:25:53 +0000 Subject: [PATCH 22/60] test cleanup --- .../functional/com/mongodb/client/WithTransactionProseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index c8a27cce9cc..78d884ca14c 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -124,7 +124,7 @@ public void testRetryTimeoutEnforcedUnknownTransactionCommit() { try (ClientSession session = client.startSession()) { ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction((TransactionBody) () -> { + session.withTransaction(() -> { ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); collection.insertOne(session, new Document("_id", 2)); return null; From 5de452b254749e13916827bca6e91332201858c9 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 22:53:30 +0000 Subject: [PATCH 23/60] retrigger checks From 1867ff5100ce721a318af55643164e18c214a74d Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 9 Dec 2025 23:16:34 +0000 Subject: [PATCH 24/60] Test cleanup --- .../com/mongodb/client/WithTransactionProseTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 78d884ca14c..e06f3c82908 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -119,7 +119,7 @@ public void testRetryTimeoutEnforcedTransientTransactionError() { public void testRetryTimeoutEnforcedUnknownTransactionCommit() { MongoDatabase failPointAdminDb = client.getDatabase("admin"); failPointAdminDb.runCommand( - Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 2}, " + Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 1}, " + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 91, 'closeConnection': false}}")); try (ClientSession session = client.startSession()) { @@ -153,7 +153,7 @@ public void testRetryTimeoutEnforcedTransientTransactionErrorOnCommit() { try (ClientSession session = client.startSession()) { ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction((TransactionBody) () -> { + session.withTransaction(() -> { ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); collection.insertOne(session, Document.parse("{ _id : 1 }")); return null; From f89d62dc4d85221c0eb720d772324aeb43cd2440 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 10 Dec 2025 00:06:08 +0000 Subject: [PATCH 25/60] retrigger checks From 3d646ae6e6aa506a02a0d7efe2c1cdff0b42c0a7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 10 Dec 2025 14:31:11 +0000 Subject: [PATCH 26/60] Update the implementation according to the spec --- .../client/internal/ClientSessionImpl.java | 21 ++++++++++++------- .../client/WithTransactionProseTest.java | 10 ++++----- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index a65d3340886..59ef120a08b 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -22,7 +22,6 @@ import com.mongodb.MongoExecutionTimeoutException; import com.mongodb.MongoInternalException; import com.mongodb.MongoOperationTimeoutException; -import com.mongodb.MongoTimeoutException; import com.mongodb.ReadConcern; import com.mongodb.TransactionOptions; import com.mongodb.WriteConcern; @@ -258,23 +257,27 @@ public T withTransaction(final TransactionBody transactionBody, final Tra long maxRetryTimeMS = timeoutMS != null ? timeoutMS : MAX_RETRY_TIME_LIMIT_MS; ExponentialBackoff transactionBackoff = null; boolean isRetry = false; + MongoException lastError = null; try { outer: while (true) { // Apply exponential backoff before retrying transaction if (isRetry) { - // Check if we've exceeded the retry time limit BEFORE applying backoff - if (ClientSessionClock.INSTANCE.now() - startTime >= maxRetryTimeMS) { - throw withTransactionTimeoutContext.hasTimeoutMS() - ? new MongoOperationTimeoutException("Transaction retry time limit of " + maxRetryTimeMS + "ms exceeded") - : new MongoTimeoutException("Transaction retry time limit of " + maxRetryTimeMS + "ms exceeded"); - } if (transactionBackoff == null) { transactionBackoff = ExponentialBackoff.forTransactionRetry(); } + // Calculate backoff delay and check if it would exceed timeout + long backoffMs = transactionBackoff.calculateDelayMs(); + if (ClientSessionClock.INSTANCE.now() - startTime + backoffMs >= maxRetryTimeMS) { + // Throw the last error as per spec + // lastError is always set here since we only retry on MongoException + throw lastError; + } try { - transactionBackoff.applyBackoff(); + if (backoffMs > 0) { + Thread.sleep(backoffMs); + } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new MongoClientException("Transaction retry interrupted", e); @@ -296,6 +299,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e); if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL) && ClientSessionClock.INSTANCE.now() - startTime < maxRetryTimeMS) { + lastError = exceptionToHandle; // Track the last error for timeout scenarios if (transactionSpan != null) { transactionSpan.spanFinalizing(false); } @@ -310,6 +314,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra commitTransaction(false); break; } catch (MongoException e) { + lastError = e; // Track the last error for timeout scenarios clearTransactionContextOnError(e); if (!(e instanceof MongoOperationTimeoutException) && ClientSessionClock.INSTANCE.now() - startTime < maxRetryTimeMS) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index e06f3c82908..45f28e586fe 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -119,12 +119,12 @@ public void testRetryTimeoutEnforcedTransientTransactionError() { public void testRetryTimeoutEnforcedUnknownTransactionCommit() { MongoDatabase failPointAdminDb = client.getDatabase("admin"); failPointAdminDb.runCommand( - Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 1}, " + Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 2}, " + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 91, 'closeConnection': false}}")); try (ClientSession session = client.startSession()) { ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction(() -> { + session.withTransaction((TransactionBody) () -> { ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); collection.insertOne(session, new Document("_id", 2)); return null; @@ -147,13 +147,13 @@ public void testRetryTimeoutEnforcedUnknownTransactionCommit() { public void testRetryTimeoutEnforcedTransientTransactionErrorOnCommit() { MongoDatabase failPointAdminDb = client.getDatabase("admin"); failPointAdminDb.runCommand( - Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 1}, " + Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 2}, " + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251, 'codeName': 'NoSuchTransaction', " + "'errmsg': 'Transaction 0 has been aborted', 'closeConnection': false}}")); try (ClientSession session = client.startSession()) { ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction(() -> { + session.withTransaction((TransactionBody) () -> { ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); collection.insertOne(session, Document.parse("{ _id : 1 }")); return null; @@ -218,8 +218,6 @@ public void testExponentialBackoffOnTransientError() { + "'errorLabels': ['TransientTransactionError']}}")); try (ClientSession session = client.startSession()) { - long startTime = System.currentTimeMillis(); - // Track retry count AtomicInteger retryCount = new AtomicInteger(0); From 9b4bf15efbd9b1c8e016921199c6f34729d03c1a Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 10 Dec 2025 16:28:47 +0000 Subject: [PATCH 27/60] Added prose test --- .../mongodb/internal/ExponentialBackoff.java | 41 ++--- .../internal/ExponentialBackoffTest.java | 152 ++++++++++++++---- .../client/WithTransactionProseTest.java | 55 +++++++ 3 files changed, 196 insertions(+), 52 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java index 518286319ad..0a01d38e271 100644 --- a/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java +++ b/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java @@ -19,6 +19,9 @@ import com.mongodb.annotations.NotThreadSafe; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.DoubleSupplier; + +import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; /** * Implements exponential backoff with jitter for retry scenarios. @@ -48,6 +51,9 @@ public final class ExponentialBackoff { private final double growthFactor; private int retryCount = 0; + // Test-only jitter supplier - when set, overrides ThreadLocalRandom + private static volatile DoubleSupplier testJitterSupplier = null; + /** * Creates an exponential backoff instance with specified parameters. * @@ -95,7 +101,10 @@ public static ExponentialBackoff forCommandRetry() { * @return delay in milliseconds */ public long calculateDelayMs() { - double jitter = ThreadLocalRandom.current().nextDouble(); + // Use test jitter supplier if set, otherwise use ThreadLocalRandom + double jitter = testJitterSupplier != null + ? testJitterSupplier.getAsDouble() + : ThreadLocalRandom.current().nextDouble(); double exponentialBackoff = baseBackoffMs * Math.pow(growthFactor, retryCount); double cappedBackoff = Math.min(exponentialBackoff, maxBackoffMs); retryCount++; @@ -103,31 +112,23 @@ public long calculateDelayMs() { } /** - * Apply backoff delay by sleeping current thread. + * Set a custom jitter supplier for testing purposes. + * This overrides the default ThreadLocalRandom jitter generation. * - * @throws InterruptedException if thread is interrupted during sleep + * @param supplier A DoubleSupplier that returns values in [0, 1) range, or null to use default */ - public void applyBackoff() throws InterruptedException { - long delayMs = calculateDelayMs(); - if (delayMs > 0) { - Thread.sleep(delayMs); - } + @VisibleForTesting(otherwise = PRIVATE) + public static void setTestJitterSupplier(final DoubleSupplier supplier) { + testJitterSupplier = supplier; } /** - * Check if applying backoff would exceed the retry time limit. - * @param startTimeMs start time of retry attempts - * @param maxRetryTimeMs maximum retry time allowed - * @return true if backoff would exceed limit, false otherwise + * Clear the test jitter supplier, reverting to default ThreadLocalRandom behavior. */ -// public boolean wouldExceedTimeLimit(final long startTimeMs, final long maxRetryTimeMs) { -// long elapsedMs = ClientSessionClock.INSTANCE.now() - startTimeMs; -// // Peek at next delay without incrementing counter -// double exponentialBackoff = baseBackoffMs * Math.pow(growthFactor, retryCount); -// double cappedBackoff = Math.min(exponentialBackoff, maxBackoffMs); -// long maxPossibleDelay = Math.round(cappedBackoff); // worst case with jitter=1 -// return elapsedMs + maxPossibleDelay > maxRetryTimeMs; -// } + @VisibleForTesting(otherwise = PRIVATE) + public static void clearTestJitterSupplier() { + testJitterSupplier = null; + } /** * Reset retry counter for new sequence of retries. diff --git a/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java index bfee96e67fb..84ab56a0e47 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java @@ -16,6 +16,7 @@ package com.mongodb.internal; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -23,6 +24,12 @@ public class ExponentialBackoffTest { + @AfterEach + void cleanup() { + // Always clear the test jitter supplier after each test to avoid test pollution + ExponentialBackoff.clearTestJitterSupplier(); + } + @Test void testTransactionRetryBackoff() { ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); @@ -73,13 +80,11 @@ void testTransactionRetryBackoffSequenceWithExpectedValues() { ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); - double[] expectedMaxValues = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, - 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; + double[] expectedMaxValues = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; for (int i = 0; i < expectedMaxValues.length; i++) { long delay = backoff.calculateDelayMs(); - assertTrue(delay >= 0 && delay <= Math.round(expectedMaxValues[i]), - String.format("Retry %d: delay should be 0-%d ms, got: %d", i, Math.round(expectedMaxValues[i]), delay)); + assertTrue(delay >= 0 && delay <= Math.round(expectedMaxValues[i]), String.format("Retry %d: delay should be 0-%d ms, got: %d", i, Math.round(expectedMaxValues[i]), delay)); } } @@ -162,32 +167,6 @@ void testReset() { assertTrue(delay >= 0 && delay <= 5, "First delay after reset should be 0-5ms, got: " + delay); } -// @Test -// void testWouldExceedTimeLimitTransactionRetry() { -// ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); -// long startTime = ClientSessionClock.INSTANCE.now(); -// -// // Initially, should not exceed time limit -// assertFalse(backoff.wouldExceedTimeLimit(startTime, 120000)); -// -// // With very little time remaining (4ms), first backoff (up to 5ms) would exceed -// long nearLimitTime = startTime - 119996; // 4ms remaining -// assertTrue(backoff.wouldExceedTimeLimit(nearLimitTime, 120000)); -// } - -// @Test -// void testWouldExceedTimeLimitCommandRetry() { -// ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); -// long startTime = ClientSessionClock.INSTANCE.now(); -// -// // Initially, should not exceed time limit -// assertFalse(backoff.wouldExceedTimeLimit(startTime, 10000)); -// -// // With 99ms remaining, first backoff (up to 100ms) would exceed -// long nearLimitTime = startTime - 9901; // 99ms remaining -// assertTrue(backoff.wouldExceedTimeLimit(nearLimitTime, 10000)); -// } - @Test void testCommandRetrySequenceMatchesSpec() { // Test that command retry follows spec: 100ms * 2^i capped at 10000ms @@ -198,8 +177,117 @@ void testCommandRetrySequenceMatchesSpec() { for (int i = 0; i < expectedMaxValues.length; i++) { long delay = backoff.calculateDelayMs(); double expectedMax = expectedMaxValues[i]; - assertTrue(delay >= 0 && delay <= Math.round(expectedMax), - String.format("Retry %d: delay should be 0-%d ms, got: %d", i, Math.round(expectedMax), delay)); + assertTrue(delay >= 0 && delay <= Math.round(expectedMax), String.format("Retry %d: delay should be 0-%d ms, got: %d", i, Math.round(expectedMax), delay)); + } + } + + // Tests for the test jitter supplier functionality + + @Test + void testJitterSupplierWithZeroJitter() { + // Set jitter to always return 0 (no backoff) + ExponentialBackoff.setTestJitterSupplier(() -> 0.0); + + ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); + + // With jitter = 0, all delays should be 0 + for (int i = 0; i < 10; i++) { + long delay = backoff.calculateDelayMs(); + assertEquals(0, delay, "With jitter=0, delay should always be 0ms"); + } + } + + @Test + void testJitterSupplierWithFullJitter() { + // Set jitter to always return 1.0 (full backoff) + ExponentialBackoff.setTestJitterSupplier(() -> 1.0); + + ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); + + // Expected delays with jitter=1.0 and growth factor 1.5 + double[] expectedDelays = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; + + for (int i = 0; i < expectedDelays.length; i++) { + long delay = backoff.calculateDelayMs(); + long expected = Math.round(expectedDelays[i]); + assertEquals(expected, delay, String.format("Retry %d: with jitter=1.0, delay should be %dms", i, expected)); + } + } + + @Test + void testJitterSupplierWithHalfJitter() { + // Set jitter to always return 0.5 (half backoff) + ExponentialBackoff.setTestJitterSupplier(() -> 0.5); + + ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); + + // Expected delays with jitter=0.5 and growth factor 1.5 + double[] expectedMaxDelays = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; + + for (int i = 0; i < expectedMaxDelays.length; i++) { + long delay = backoff.calculateDelayMs(); + long expected = Math.round(0.5 * expectedMaxDelays[i]); + assertEquals(expected, delay, String.format("Retry %d: with jitter=0.5, delay should be %dms", i, expected)); + } + } + + @Test + void testJitterSupplierForCommandRetry() { + // Test that custom jitter also works with command retry configuration + ExponentialBackoff.setTestJitterSupplier(() -> 1.0); + + ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); + + // Expected first few delays with jitter=1.0 and growth factor 2.0 + long[] expectedDelays = {100, 200, 400, 800, 1600, 3200, 6400, 10000}; + + for (int i = 0; i < expectedDelays.length; i++) { + long delay = backoff.calculateDelayMs(); + assertEquals(expectedDelays[i], delay, String.format("Command retry %d: with jitter=1.0, delay should be %dms", i, expectedDelays[i])); + } + } + + @Test + void testClearingJitterSupplierReturnsToRandom() { + // First set a fixed jitter + ExponentialBackoff.setTestJitterSupplier(() -> 0.0); + + ExponentialBackoff backoff1 = ExponentialBackoff.forTransactionRetry(); + long delay1 = backoff1.calculateDelayMs(); + assertEquals(0, delay1, "With jitter=0, delay should be 0ms"); + + // Clear the test jitter supplier + ExponentialBackoff.clearTestJitterSupplier(); + + // Now delays should be random again + ExponentialBackoff backoff2 = ExponentialBackoff.forTransactionRetry(); + + // Run multiple times to verify randomness (statistically very unlikely to get all zeros) + boolean foundNonZero = false; + for (int i = 0; i < 20; i++) { + long delay = backoff2.calculateDelayMs(); + assertTrue(delay >= 0 && delay <= Math.round(5.0 * Math.pow(1.5, i)), "Delay should be within expected range"); + if (delay > 0) { + foundNonZero = true; + } } + assertTrue(foundNonZero, "After clearing test jitter, should get some non-zero delays (random behavior)"); + } + + @Test + void testJitterSupplierWithCustomBackoff() { + // Test that custom jitter works with custom backoff parameters + ExponentialBackoff.setTestJitterSupplier(() -> 0.75); + + ExponentialBackoff backoff = new ExponentialBackoff(100.0, 1000.0, 2.5); + + // First delay: 0.75 * 100 = 75 + assertEquals(75, backoff.calculateDelayMs()); + + // Second delay: 0.75 * 100 * 2.5 = 0.75 * 250 = 188 (rounded) + assertEquals(188, backoff.calculateDelayMs()); + + // Third delay: 0.75 * 100 * 2.5^2 = 0.75 * 625 = 469 (rounded) + assertEquals(469, backoff.calculateDelayMs()); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 45f28e586fe..43292321ead 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -22,6 +22,7 @@ import com.mongodb.TransactionOptions; import com.mongodb.client.internal.ClientSessionClock; import com.mongodb.client.model.Sorts; +import com.mongodb.internal.ExponentialBackoff; import org.bson.Document; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -233,6 +234,60 @@ public void testExponentialBackoffOnTransientError() { } } + // + // Test that retries within withTransaction do not occur immediately + // This test verifies that exponential backoff is enforced during commit retries + // See: https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/tests/README.md#retry-backoff-is-enforced + // + @Test + public void testRetryBackoffIsEnforced() { + MongoDatabase failPointAdminDb = client.getDatabase("admin"); + + // Test 1: Run with jitter = 0 (no backoff) + ExponentialBackoff.setTestJitterSupplier(() -> 0.0); + + failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251}}")); + + long noBackoffTime; + try (ClientSession session = client.startSession()) { + long startNanos = System.nanoTime(); + session.withTransaction(() -> { + collection.insertOne(session, Document.parse("{ _id : 'backoff-test-no-jitter' }")); + return null; + }); + long endNanos = System.nanoTime(); + noBackoffTime = TimeUnit.NANOSECONDS.toMillis(endNanos - startNanos); + } finally { + // Clear the test jitter supplier to avoid affecting other tests + ExponentialBackoff.clearTestJitterSupplier(); + failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); + } + + // Test 2: Run with jitter = 1 (full backoff) + ExponentialBackoff.setTestJitterSupplier(() -> 1.0); + + failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251}}")); + + long withBackoffTime; + try (ClientSession session = client.startSession()) { + long startNanos = System.nanoTime(); + session.withTransaction(() -> { + collection.insertOne(session, Document.parse("{ _id : 'backoff-test-full-jitter' }")); + return null; + }); + long endNanos = System.nanoTime(); + withBackoffTime = TimeUnit.NANOSECONDS.toMillis(endNanos - startNanos); + } finally { + ExponentialBackoff.clearTestJitterSupplier(); + failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); + } + + long expectedWithBackoffTime = noBackoffTime + 2200; // 2.2 seconds as per spec + long actualDifference = Math.abs(withBackoffTime - expectedWithBackoffTime); + + assertTrue(actualDifference < 1000, String.format("Expected withBackoffTime to be ~%dms (noBackoffTime %dms + 2200ms), " + "but got %dms. Difference: %dms (tolerance: 1000ms per spec)", expectedWithBackoffTime, noBackoffTime, withBackoffTime, actualDifference)); + } + private boolean canRunTests() { return isSharded() || isDiscoverableReplicaSet(); } From da837048c1d5a8f38e7028d5935424020d33f18d Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 10 Dec 2025 18:16:26 +0000 Subject: [PATCH 28/60] Flaky test --- .../client/AbstractClientSideOperationsTimeoutProseTest.java | 3 +++ .../com/mongodb/client/WithTransactionProseTest.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 9ce58b1654f..094ae51f5d2 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -675,6 +675,9 @@ public void test10CustomTestWithTransactionUsesASingleTimeout() { } @DisplayName("10. Convenient Transactions - Custom Test: with transaction uses a single timeout - lock") + // The timing of when the timeout check occurred relative to the retry attempts and backoff delays could vary based on system load and jitter values, sometimes allowing + // the LockTimeout error to surface before the timeout was detected. + @FlakyTest(maxAttempts = 3) @Test public void test10CustomTestWithTransactionUsesASingleTimeoutWithLock() { assumeTrue(serverVersionAtLeast(4, 4)); diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 43292321ead..bcd52025ac2 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -25,6 +25,7 @@ import com.mongodb.internal.ExponentialBackoff; import org.bson.Document; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; @@ -239,6 +240,7 @@ public void testExponentialBackoffOnTransientError() { // This test verifies that exponential backoff is enforced during commit retries // See: https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/tests/README.md#retry-backoff-is-enforced // + @DisplayName("Retry Backoff is Enforced") @Test public void testRetryBackoffIsEnforced() { MongoDatabase failPointAdminDb = client.getDatabase("admin"); From cb951671b3dc05d6e7f6c74247dced329b5fb977 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 11 Dec 2025 11:51:59 +0000 Subject: [PATCH 29/60] Remove extra Test annotation --- .../client/AbstractClientSideOperationsTimeoutProseTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 094ae51f5d2..b4694e143a0 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -678,7 +678,6 @@ public void test10CustomTestWithTransactionUsesASingleTimeout() { // The timing of when the timeout check occurred relative to the retry attempts and backoff delays could vary based on system load and jitter values, sometimes allowing // the LockTimeout error to surface before the timeout was detected. @FlakyTest(maxAttempts = 3) - @Test public void test10CustomTestWithTransactionUsesASingleTimeoutWithLock() { assumeTrue(serverVersionAtLeast(4, 4)); assumeFalse(isStandalone()); From ef734a06f7139d75e67f84254bf500e2bdacf991 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Sun, 14 Dec 2025 16:06:12 +0000 Subject: [PATCH 30/60] Throwing correct exception when CSOT is used --- .../mongodb/client/internal/ClientSessionImpl.java | 12 +++++++++--- ...AbstractClientSideOperationsTimeoutProseTest.java | 4 +--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index 59ef120a08b..5798f8afd80 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -270,9 +270,15 @@ public T withTransaction(final TransactionBody transactionBody, final Tra // Calculate backoff delay and check if it would exceed timeout long backoffMs = transactionBackoff.calculateDelayMs(); if (ClientSessionClock.INSTANCE.now() - startTime + backoffMs >= maxRetryTimeMS) { - // Throw the last error as per spec - // lastError is always set here since we only retry on MongoException - throw lastError; + // If CSOT is enabled (timeoutMS is set), throw MongoOperationTimeoutException + // Otherwise, throw the last error directly for backward compatibility + if (timeoutMS != null) { + throw new MongoOperationTimeoutException( + "Transaction retry timeout exceeded after " + (ClientSessionClock.INSTANCE.now() - startTime) + "ms", + lastError); + } else { + throw lastError; + } } try { if (backoffMs > 0) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index b4694e143a0..9ce58b1654f 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -675,9 +675,7 @@ public void test10CustomTestWithTransactionUsesASingleTimeout() { } @DisplayName("10. Convenient Transactions - Custom Test: with transaction uses a single timeout - lock") - // The timing of when the timeout check occurred relative to the retry attempts and backoff delays could vary based on system load and jitter values, sometimes allowing - // the LockTimeout error to surface before the timeout was detected. - @FlakyTest(maxAttempts = 3) + @Test public void test10CustomTestWithTransactionUsesASingleTimeoutWithLock() { assumeTrue(serverVersionAtLeast(4, 4)); assumeFalse(isStandalone()); From 96b5ed7e5cf7c2b249be7f5eca46dceb14772762 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Sun, 14 Dec 2025 17:51:47 +0000 Subject: [PATCH 31/60] Simplifying implementation by relying on CSOT to throw when timeout is exceeded (ex operationContext.getTimeoutContext().getReadTimeoutMS()) --- .../client/internal/ClientSessionImpl.java | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index 5798f8afd80..a68ec9bc69e 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -27,8 +27,10 @@ import com.mongodb.WriteConcern; import com.mongodb.client.ClientSession; import com.mongodb.client.TransactionBody; -import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.ExponentialBackoff; +import com.mongodb.internal.TimeoutContext; +import com.mongodb.internal.observability.micrometer.TracingManager; +import com.mongodb.internal.observability.micrometer.TransactionSpan; import com.mongodb.internal.operation.AbortTransactionOperation; import com.mongodb.internal.operation.CommitTransactionOperation; import com.mongodb.internal.operation.OperationHelper; @@ -37,8 +39,6 @@ import com.mongodb.internal.operation.WriteOperation; import com.mongodb.internal.session.BaseClientSessionImpl; import com.mongodb.internal.session.ServerSessionPool; -import com.mongodb.internal.observability.micrometer.TracingManager; -import com.mongodb.internal.observability.micrometer.TransactionSpan; import com.mongodb.lang.Nullable; import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL; @@ -252,12 +252,8 @@ public T withTransaction(final TransactionBody transactionBody, final Tra notNull("transactionBody", transactionBody); long startTime = ClientSessionClock.INSTANCE.now(); TimeoutContext withTransactionTimeoutContext = createTimeoutContext(options); - // Use CSOT timeout if set, otherwise default to MAX_RETRY_TIME_LIMIT_MS - Long timeoutMS = withTransactionTimeoutContext.getTimeoutSettings().getTimeoutMS(); - long maxRetryTimeMS = timeoutMS != null ? timeoutMS : MAX_RETRY_TIME_LIMIT_MS; ExponentialBackoff transactionBackoff = null; boolean isRetry = false; - MongoException lastError = null; try { outer: @@ -267,19 +263,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra if (transactionBackoff == null) { transactionBackoff = ExponentialBackoff.forTransactionRetry(); } - // Calculate backoff delay and check if it would exceed timeout long backoffMs = transactionBackoff.calculateDelayMs(); - if (ClientSessionClock.INSTANCE.now() - startTime + backoffMs >= maxRetryTimeMS) { - // If CSOT is enabled (timeoutMS is set), throw MongoOperationTimeoutException - // Otherwise, throw the last error directly for backward compatibility - if (timeoutMS != null) { - throw new MongoOperationTimeoutException( - "Transaction retry timeout exceeded after " + (ClientSessionClock.INSTANCE.now() - startTime) + "ms", - lastError); - } else { - throw lastError; - } - } try { if (backoffMs > 0) { Thread.sleep(backoffMs); @@ -304,8 +288,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra if (e instanceof MongoException && !(e instanceof MongoOperationTimeoutException)) { MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e); if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL) - && ClientSessionClock.INSTANCE.now() - startTime < maxRetryTimeMS) { - lastError = exceptionToHandle; // Track the last error for timeout scenarios + && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { if (transactionSpan != null) { transactionSpan.spanFinalizing(false); } @@ -320,10 +303,9 @@ public T withTransaction(final TransactionBody transactionBody, final Tra commitTransaction(false); break; } catch (MongoException e) { - lastError = e; // Track the last error for timeout scenarios clearTransactionContextOnError(e); if (!(e instanceof MongoOperationTimeoutException) - && ClientSessionClock.INSTANCE.now() - startTime < maxRetryTimeMS) { + && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { applyMajorityWriteConcernToTransactionOptions(); if (!(e instanceof MongoExecutionTimeoutException) From 43eda52181273ba553402849553dea62ba9e79df Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 13 Jan 2026 15:52:49 +0000 Subject: [PATCH 32/60] Fixing implementation according to spec changes in JAVA-6046 and https://github.com/mongodb/specifications/pull/1868 --- .../mongodb/internal/ExponentialBackoff.java | 16 ++++++++ .../client/internal/ClientSessionImpl.java | 39 ++++++++++++------- .../client/WithTransactionProseTest.java | 6 ++- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java index 0a01d38e271..42a9ff1d385 100644 --- a/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java +++ b/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java @@ -111,6 +111,22 @@ public long calculateDelayMs() { return Math.round(jitter * cappedBackoff); } + /** + * Calculate backoff delay with jitter for a specific retry count. + * This method does not modify the internal retry counter. + * + * @param retryCount the retry count to calculate delay for + * @return delay in milliseconds + */ + public long calculateDelayMs(final int retryCount) { + double jitter = testJitterSupplier != null + ? testJitterSupplier.getAsDouble() + : ThreadLocalRandom.current().nextDouble(); + double exponentialBackoff = baseBackoffMs * Math.pow(growthFactor, retryCount); + double cappedBackoff = Math.min(exponentialBackoff, maxBackoffMs); + return Math.round(jitter * cappedBackoff); + } + /** * Set a custom jitter supplier for testing purposes. * This overrides the default ThreadLocalRandom jitter generation. diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index a68ec9bc69e..ddbc0ecf205 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -252,18 +252,22 @@ public T withTransaction(final TransactionBody transactionBody, final Tra notNull("transactionBody", transactionBody); long startTime = ClientSessionClock.INSTANCE.now(); TimeoutContext withTransactionTimeoutContext = createTimeoutContext(options); - ExponentialBackoff transactionBackoff = null; - boolean isRetry = false; + ExponentialBackoff transactionBackoff = ExponentialBackoff.forTransactionRetry(); + int transactionAttempt = 0; + MongoException lastError = null; try { outer: while (true) { - // Apply exponential backoff before retrying transaction - if (isRetry) { - if (transactionBackoff == null) { - transactionBackoff = ExponentialBackoff.forTransactionRetry(); + if (transactionAttempt > 0) { + long backoffMs = transactionBackoff.calculateDelayMs(transactionAttempt - 1); + // Check if backoff would exceed timeout + if (ClientSessionClock.INSTANCE.now() + backoffMs - startTime >= MAX_RETRY_TIME_LIMIT_MS) { + if (lastError != null) { + throw lastError; + } + throw new MongoClientException("Transaction retry timeout exceeded"); } - long backoffMs = transactionBackoff.calculateDelayMs(); try { if (backoffMs > 0) { Thread.sleep(backoffMs); @@ -273,10 +277,11 @@ public T withTransaction(final TransactionBody transactionBody, final Tra throw new MongoClientException("Transaction retry interrupted", e); } } - isRetry = true; T retVal; try { startTransaction(options, withTransactionTimeoutContext.copyTimeoutContext()); + transactionAttempt++; + if (transactionSpan != null) { transactionSpan.setIsConvenientTransaction(); } @@ -285,14 +290,17 @@ public T withTransaction(final TransactionBody transactionBody, final Tra if (transactionState == TransactionState.IN) { abortTransaction(); } - if (e instanceof MongoException && !(e instanceof MongoOperationTimeoutException)) { - MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e); - if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL) - && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { - if (transactionSpan != null) { - transactionSpan.spanFinalizing(false); + if (e instanceof MongoException) { + lastError = (MongoException) e; // Store last error + if (!(e instanceof MongoOperationTimeoutException)) { + MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e); + if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL) + && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { + if (transactionSpan != null) { + transactionSpan.spanFinalizing(false); + } + continue; } - continue; } } throw e; @@ -315,6 +323,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra if (transactionSpan != null) { transactionSpan.spanFinalizing(true); } + lastError = e; continue outer; } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index bcd52025ac2..5d3b3b176df 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -284,10 +284,12 @@ public void testRetryBackoffIsEnforced() { failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); } - long expectedWithBackoffTime = noBackoffTime + 2200; // 2.2 seconds as per spec + long expectedWithBackoffTime = noBackoffTime + 1800; // 1.8 seconds as per spec long actualDifference = Math.abs(withBackoffTime - expectedWithBackoffTime); - assertTrue(actualDifference < 1000, String.format("Expected withBackoffTime to be ~%dms (noBackoffTime %dms + 2200ms), " + "but got %dms. Difference: %dms (tolerance: 1000ms per spec)", expectedWithBackoffTime, noBackoffTime, withBackoffTime, actualDifference)); + assertTrue(actualDifference < 1000, String.format("Expected withBackoffTime to be ~%dms (noBackoffTime %dms + 1800ms), but" + + " got %dms. Difference: %dms (tolerance: 1000ms per spec)", expectedWithBackoffTime, noBackoffTime, withBackoffTime, + actualDifference)); } private boolean canRunTests() { From a4193dd2ff4bf5c82b027a4b821a2149145c6e47 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 13 Jan 2026 23:47:24 +0000 Subject: [PATCH 33/60] Update driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java Co-authored-by: Valentin Kovalenko --- .../com/mongodb/client/WithTransactionProseTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 5d3b3b176df..50c1a69f90a 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -225,8 +225,7 @@ public void testExponentialBackoffOnTransientError() { session.withTransaction(() -> { retryCount.incrementAndGet(); // Count the attempt before the operation that might fail - collection.insertOne(session, Document.parse("{ _id : 'backoff-test' }")); - return retryCount; + return collection.insertOne(session, Document.parse("{ _id : 'backoff-test' }")); }); assertEquals(4, retryCount.get(), "Expected 1 initial attempt + 3 retries"); From f2d826310e7f2383a3c7b5375e30d5b0d0d26c49 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 14 Jan 2026 00:00:25 +0000 Subject: [PATCH 34/60] Update driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java Co-authored-by: Valentin Kovalenko --- .../functional/com/mongodb/client/WithTransactionProseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 50c1a69f90a..9453ff09b2c 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -211,7 +211,7 @@ public void testTimeoutMSAndLegacySettings() { // Backoff uses growth factor of 1.5 as per spec // @Test - public void testExponentialBackoffOnTransientError() { + void testExponentialBackoffOnTransientError() { // Configure failpoint to simulate transient errors MongoDatabase failPointAdminDb = client.getDatabase("admin"); failPointAdminDb.runCommand( From f3daab0af5c9e2415b0d37de376c41c9e09b290c Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 14 Jan 2026 00:01:09 +0000 Subject: [PATCH 35/60] Update driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java Co-authored-by: Valentin Kovalenko --- .../com/mongodb/client/WithTransactionProseTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 9453ff09b2c..9c917ac7771 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -276,8 +276,7 @@ public void testRetryBackoffIsEnforced() { collection.insertOne(session, Document.parse("{ _id : 'backoff-test-full-jitter' }")); return null; }); - long endNanos = System.nanoTime(); - withBackoffTime = TimeUnit.NANOSECONDS.toMillis(endNanos - startNanos); + withBackoffTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos); } finally { ExponentialBackoff.clearTestJitterSupplier(); failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); From 7afd9d41383a7b2afdd19e6a31da6c92bd42aaa4 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 14 Jan 2026 00:01:52 +0000 Subject: [PATCH 36/60] Update driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java Co-authored-by: Valentin Kovalenko --- .../functional/com/mongodb/client/WithTransactionProseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 9c917ac7771..e38d99fd014 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -241,7 +241,7 @@ void testExponentialBackoffOnTransientError() { // @DisplayName("Retry Backoff is Enforced") @Test - public void testRetryBackoffIsEnforced() { + void testRetryBackoffIsEnforced() { MongoDatabase failPointAdminDb = client.getDatabase("admin"); // Test 1: Run with jitter = 0 (no backoff) From ccb8d030fb84108bdb6743c0020bd8eb691d5c9f Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 14 Jan 2026 00:03:12 +0000 Subject: [PATCH 37/60] Update driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java Co-authored-by: Valentin Kovalenko --- .../com/mongodb/client/WithTransactionProseTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index e38d99fd014..2441f13a741 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -256,8 +256,7 @@ void testRetryBackoffIsEnforced() { collection.insertOne(session, Document.parse("{ _id : 'backoff-test-no-jitter' }")); return null; }); - long endNanos = System.nanoTime(); - noBackoffTime = TimeUnit.NANOSECONDS.toMillis(endNanos - startNanos); + noBackoffTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);``` } finally { // Clear the test jitter supplier to avoid affecting other tests ExponentialBackoff.clearTestJitterSupplier(); From bbb9a68770c4cfe7d9dc59a1b2b9b7fa8afb0374 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 14 Jan 2026 00:05:21 +0000 Subject: [PATCH 38/60] Update driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java Co-authored-by: Valentin Kovalenko --- .../com/mongodb/client/WithTransactionProseTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 2441f13a741..0bcb5a417fa 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -271,10 +271,7 @@ void testRetryBackoffIsEnforced() { long withBackoffTime; try (ClientSession session = client.startSession()) { long startNanos = System.nanoTime(); - session.withTransaction(() -> { - collection.insertOne(session, Document.parse("{ _id : 'backoff-test-full-jitter' }")); - return null; - }); + session.withTransaction(() -> collection.insertOne(session, Document.parse("{ _id : 'backoff-test-full-jitter' }"))); withBackoffTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos); } finally { ExponentialBackoff.clearTestJitterSupplier(); From c44872db5fbe7a140de561a5c2ff9d9b259e6bf0 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 14 Jan 2026 00:06:46 +0000 Subject: [PATCH 39/60] Update driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java Co-authored-by: Valentin Kovalenko --- .../com/mongodb/client/WithTransactionProseTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 0bcb5a417fa..92ad8792c33 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -252,10 +252,7 @@ void testRetryBackoffIsEnforced() { long noBackoffTime; try (ClientSession session = client.startSession()) { long startNanos = System.nanoTime(); - session.withTransaction(() -> { - collection.insertOne(session, Document.parse("{ _id : 'backoff-test-no-jitter' }")); - return null; - }); + session.withTransaction(() -> collection.insertOne(session, Document.parse("{ _id : 'backoff-test-no-jitter' }"))); noBackoffTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);``` } finally { // Clear the test jitter supplier to avoid affecting other tests From f0dd916048dc3c02a096ceb15703d205e47dd991 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 14 Jan 2026 01:02:28 +0000 Subject: [PATCH 40/60] Update driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java Co-authored-by: Valentin Kovalenko --- .../src/main/com/mongodb/internal/ExponentialBackoff.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java index 42a9ff1d385..0ac1e12ee4e 100644 --- a/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java +++ b/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java @@ -69,7 +69,8 @@ public ExponentialBackoff(final double baseBackoffMs, final double maxBackoffMs, /** * Creates a backoff instance configured for withTransaction retries. - * Uses: 5ms base, 500ms max, 1.5 growth factor. + * Uses: {@value TRANSACTION_BASE_BACKOFF_MS} ms base, {@value TRANSACTION_MAX_BACKOFF_MS} ms max, + * {@value TRANSACTION_BACKOFF_GROWTH} growth factor. * * @return ExponentialBackoff configured for transaction retries */ From 0e00c90cc6bb0b065c70d35f9289fd675b5a7d04 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 15 Jan 2026 23:25:48 +0000 Subject: [PATCH 41/60] PR feedback --- .../mongodb/internal/ExponentialBackoff.java | 192 ------------- .../internal/time/ExponentialBackoff.java | 88 ++++++ .../internal/ExponentialBackoffTest.java | 268 ++---------------- .../client/internal/ClientSessionImpl.java | 40 +-- .../client/WithTransactionProseTest.java | 97 +++---- 5 files changed, 180 insertions(+), 505 deletions(-) delete mode 100644 driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java create mode 100644 driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java diff --git a/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java deleted file mode 100644 index 0ac1e12ee4e..00000000000 --- a/driver-core/src/main/com/mongodb/internal/ExponentialBackoff.java +++ /dev/null @@ -1,192 +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.internal; - -import com.mongodb.annotations.NotThreadSafe; - -import java.util.concurrent.ThreadLocalRandom; -import java.util.function.DoubleSupplier; - -import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; - -/** - * Implements exponential backoff with jitter for retry scenarios. - * Formula: delayMS = jitter * min(maxBackoffMs, baseBackoffMs * growthFactor^retryCount) - * where jitter is random value [0, 1). - * - *

This class provides factory methods for common use cases: - *

    - *
  • {@link #forTransactionRetry()} - For withTransaction retries (5ms base, 500ms max, 1.5 growth)
  • - *
  • {@link #forCommandRetry()} - For command retries with overload (100ms base, 10000ms max, 2.0 growth)
  • - *
- */ -@NotThreadSafe -public final class ExponentialBackoff { - // Transaction retry constants (per spec) - private static final double TRANSACTION_BASE_BACKOFF_MS = 5.0; - private static final double TRANSACTION_MAX_BACKOFF_MS = 500.0; - private static final double TRANSACTION_BACKOFF_GROWTH = 1.5; - - // Command retry constants (per spec) - private static final double COMMAND_BASE_BACKOFF_MS = 100.0; - private static final double COMMAND_MAX_BACKOFF_MS = 10000.0; - private static final double COMMAND_BACKOFF_GROWTH = 2.0; - - private final double baseBackoffMs; - private final double maxBackoffMs; - private final double growthFactor; - private int retryCount = 0; - - // Test-only jitter supplier - when set, overrides ThreadLocalRandom - private static volatile DoubleSupplier testJitterSupplier = null; - - /** - * Creates an exponential backoff instance with specified parameters. - * - * @param baseBackoffMs Initial backoff in milliseconds - * @param maxBackoffMs Maximum backoff cap in milliseconds - * @param growthFactor Exponential growth factor (e.g., 1.5 or 2.0) - */ - public ExponentialBackoff(final double baseBackoffMs, final double maxBackoffMs, final double growthFactor) { - this.baseBackoffMs = baseBackoffMs; - this.maxBackoffMs = maxBackoffMs; - this.growthFactor = growthFactor; - } - - /** - * Creates a backoff instance configured for withTransaction retries. - * Uses: {@value TRANSACTION_BASE_BACKOFF_MS} ms base, {@value TRANSACTION_MAX_BACKOFF_MS} ms max, - * {@value TRANSACTION_BACKOFF_GROWTH} growth factor. - * - * @return ExponentialBackoff configured for transaction retries - */ - public static ExponentialBackoff forTransactionRetry() { - return new ExponentialBackoff( - TRANSACTION_BASE_BACKOFF_MS, - TRANSACTION_MAX_BACKOFF_MS, - TRANSACTION_BACKOFF_GROWTH - ); - } - - /** - * Creates a backoff instance configured for command retries during overload. - * Uses: 100ms base, 10000ms max, 2.0 growth factor. - * - * @return ExponentialBackoff configured for command retries - */ - public static ExponentialBackoff forCommandRetry() { - return new ExponentialBackoff( - COMMAND_BASE_BACKOFF_MS, - COMMAND_MAX_BACKOFF_MS, - COMMAND_BACKOFF_GROWTH - ); - } - - /** - * Calculate next backoff delay with jitter. - * - * @return delay in milliseconds - */ - public long calculateDelayMs() { - // Use test jitter supplier if set, otherwise use ThreadLocalRandom - double jitter = testJitterSupplier != null - ? testJitterSupplier.getAsDouble() - : ThreadLocalRandom.current().nextDouble(); - double exponentialBackoff = baseBackoffMs * Math.pow(growthFactor, retryCount); - double cappedBackoff = Math.min(exponentialBackoff, maxBackoffMs); - retryCount++; - return Math.round(jitter * cappedBackoff); - } - - /** - * Calculate backoff delay with jitter for a specific retry count. - * This method does not modify the internal retry counter. - * - * @param retryCount the retry count to calculate delay for - * @return delay in milliseconds - */ - public long calculateDelayMs(final int retryCount) { - double jitter = testJitterSupplier != null - ? testJitterSupplier.getAsDouble() - : ThreadLocalRandom.current().nextDouble(); - double exponentialBackoff = baseBackoffMs * Math.pow(growthFactor, retryCount); - double cappedBackoff = Math.min(exponentialBackoff, maxBackoffMs); - return Math.round(jitter * cappedBackoff); - } - - /** - * Set a custom jitter supplier for testing purposes. - * This overrides the default ThreadLocalRandom jitter generation. - * - * @param supplier A DoubleSupplier that returns values in [0, 1) range, or null to use default - */ - @VisibleForTesting(otherwise = PRIVATE) - public static void setTestJitterSupplier(final DoubleSupplier supplier) { - testJitterSupplier = supplier; - } - - /** - * Clear the test jitter supplier, reverting to default ThreadLocalRandom behavior. - */ - @VisibleForTesting(otherwise = PRIVATE) - public static void clearTestJitterSupplier() { - testJitterSupplier = null; - } - - /** - * Reset retry counter for new sequence of retries. - */ - public void reset() { - retryCount = 0; - } - - /** - * Get current retry count for testing. - * - * @return current retry count - */ - public int getRetryCount() { - return retryCount; - } - - /** - * Get the base backoff in milliseconds. - * - * @return base backoff - */ - public double getBaseBackoffMs() { - return baseBackoffMs; - } - - /** - * Get the maximum backoff in milliseconds. - * - * @return maximum backoff - */ - public double getMaxBackoffMs() { - return maxBackoffMs; - } - - /** - * Get the growth factor. - * - * @return growth factor - */ - public double getGrowthFactor() { - return growthFactor; - } -} diff --git a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java new file mode 100644 index 00000000000..2a69e8ba9e2 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java @@ -0,0 +1,88 @@ +/* + * 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.internal.time; + +import com.mongodb.annotations.NotThreadSafe; +import com.mongodb.internal.VisibleForTesting; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.DoubleSupplier; + +import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; + +/** + * Implements exponential backoff with jitter for retry scenarios. + */ +@NotThreadSafe +public enum ExponentialBackoff { + TRANSACTION(5.0, 500.0, 1.5); + + private final double baseMs, maxMs, growth; + + // TODO remove this global state once https://jira.mongodb.org/browse/JAVA-6060 is done + private static DoubleSupplier testJitterSupplier = null; + + ExponentialBackoff(final double baseMs, final double maxMs, final double growth) { + this.baseMs = baseMs; + this.maxMs = maxMs; + this.growth = growth; + } + + /** + * Calculate the next delay in milliseconds based on the retry count. + * + * @param retryCount The number of retries that have occurred. + * @return The calculated delay in milliseconds. + */ + public long calculateDelayBeforeNextRetryMs(final int retryCount) { + double jitter = testJitterSupplier != null + ? testJitterSupplier.getAsDouble() + : ThreadLocalRandom.current().nextDouble(); + double backoff = Math.min(baseMs * Math.pow(growth, retryCount), maxMs); + return Math.round(jitter * backoff); + } + + /** + * Calculate the next delay in milliseconds based on the retry count and a provided jitter. + * + * @param retryCount The number of retries that have occurred. + * @param jitter A double in the range [0, 1) to apply as jitter. + * @return The calculated delay in milliseconds. + */ + public long calculateDelayBeforeNextRetryMs(final int retryCount, final double jitter) { + double backoff = Math.min(baseMs * Math.pow(growth, retryCount), maxMs); + return Math.round(jitter * backoff); + } + + /** + * Set a custom jitter supplier for testing purposes. + * + * @param supplier A DoubleSupplier that returns values in [0, 1) range. + */ + @VisibleForTesting(otherwise = PRIVATE) + public static void setTestJitterSupplier(final DoubleSupplier supplier) { + testJitterSupplier = supplier; + } + + /** + * Clear the test jitter supplier, reverting to default ThreadLocalRandom behavior. + */ + @VisibleForTesting(otherwise = PRIVATE) + public static void clearTestJitterSupplier() { + testJitterSupplier = null; + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java index 84ab56a0e47..67238532488 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java @@ -16,7 +16,7 @@ package com.mongodb.internal; -import org.junit.jupiter.api.AfterEach; +import com.mongodb.internal.time.ExponentialBackoff; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -24,270 +24,50 @@ public class ExponentialBackoffTest { - @AfterEach - void cleanup() { - // Always clear the test jitter supplier after each test to avoid test pollution - ExponentialBackoff.clearTestJitterSupplier(); - } - @Test void testTransactionRetryBackoff() { - ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); - - // Verify configuration - assertEquals(5.0, backoff.getBaseBackoffMs()); - assertEquals(500.0, backoff.getMaxBackoffMs()); - assertEquals(1.5, backoff.getGrowthFactor()); - - // First retry (i=0): delay = jitter * min(5 * 1.5^0, 500) = jitter * 5 - // Since jitter is random [0,1), the delay should be between 0 and 5ms - long delay1 = backoff.calculateDelayMs(); - assertTrue(delay1 >= 0 && delay1 <= 5, "First delay should be 0-5ms, got: " + delay1); - - // Second retry (i=1): delay = jitter * min(5 * 1.5^1, 500) = jitter * 7.5 - long delay2 = backoff.calculateDelayMs(); - assertTrue(delay2 >= 0 && delay2 <= 8, "Second delay should be 0-8ms, got: " + delay2); - - // Third retry (i=2): delay = jitter * min(5 * 1.5^2, 500) = jitter * 11.25 - long delay3 = backoff.calculateDelayMs(); - assertTrue(delay3 >= 0 && delay3 <= 12, "Third delay should be 0-12ms, got: " + delay3); - - // Verify the retry count is incrementing properly - assertEquals(3, backoff.getRetryCount()); - } - - @Test - void testTransactionRetryBackoffRespectsMaximum() { - ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); - - // Advance to a high retry count where backoff would exceed 500ms without capping - for (int i = 0; i < 20; i++) { - backoff.calculateDelayMs(); - } - - // Even at high retry counts, delay should never exceed 500ms - for (int i = 0; i < 5; i++) { - long delay = backoff.calculateDelayMs(); - assertTrue(delay >= 0 && delay <= 500, "Delay should be capped at 500ms, got: " + delay); - } - } - - @Test - void testTransactionRetryBackoffSequenceWithExpectedValues() { // Test that the backoff sequence follows the expected pattern with growth factor 1.5 - // Expected sequence (without jitter): 5, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, ... + // Expected sequence (without jitter): 5, 7.5, 11.25, ... // With jitter, actual values will be between 0 and these maxima - - ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); - double[] expectedMaxValues = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; - for (int i = 0; i < expectedMaxValues.length; i++) { - long delay = backoff.calculateDelayMs(); - assertTrue(delay >= 0 && delay <= Math.round(expectedMaxValues[i]), String.format("Retry %d: delay should be 0-%d ms, got: %d", i, Math.round(expectedMaxValues[i]), delay)); + ExponentialBackoff backoff = ExponentialBackoff.TRANSACTION; + for (int retry = 0; retry < expectedMaxValues.length; retry++) { + long delay = backoff.calculateDelayBeforeNextRetryMs(retry); + assertTrue(delay >= 0 && delay <= Math.round(expectedMaxValues[retry]), String.format("Retry %d: delay should be 0-%d ms, got: %d", retry, Math.round(expectedMaxValues[retry]), delay)); } } @Test - void testCommandRetryBackoff() { - ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); - - // Verify configuration - assertEquals(100.0, backoff.getBaseBackoffMs()); - assertEquals(10000.0, backoff.getMaxBackoffMs()); - assertEquals(2.0, backoff.getGrowthFactor()); - - // Test sequence with growth factor 2.0 - // Expected max delays: 100, 200, 400, 800, 1600, 3200, 6400, 10000 (capped) - long delay1 = backoff.calculateDelayMs(); - assertTrue(delay1 >= 0 && delay1 <= 100, "First delay should be 0-100ms, got: " + delay1); - - long delay2 = backoff.calculateDelayMs(); - assertTrue(delay2 >= 0 && delay2 <= 200, "Second delay should be 0-200ms, got: " + delay2); - - long delay3 = backoff.calculateDelayMs(); - assertTrue(delay3 >= 0 && delay3 <= 400, "Third delay should be 0-400ms, got: " + delay3); - - long delay4 = backoff.calculateDelayMs(); - assertTrue(delay4 >= 0 && delay4 <= 800, "Fourth delay should be 0-800ms, got: " + delay4); - - long delay5 = backoff.calculateDelayMs(); - assertTrue(delay5 >= 0 && delay5 <= 1600, "Fifth delay should be 0-1600ms, got: " + delay5); - } - - @Test - void testCommandRetryBackoffRespectsMaximum() { - ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); - - // Advance to where exponential would exceed 10000ms - for (int i = 0; i < 10; i++) { - backoff.calculateDelayMs(); - } - - // Even at high retry counts, delay should never exceed 10000ms - for (int i = 0; i < 5; i++) { - long delay = backoff.calculateDelayMs(); - assertTrue(delay >= 0 && delay <= 10000, "Delay should be capped at 10000ms, got: " + delay); - } - } - - @Test - void testCustomBackoff() { - // Test with custom parameters - ExponentialBackoff backoff = new ExponentialBackoff(50.0, 2000.0, 1.8); - - assertEquals(50.0, backoff.getBaseBackoffMs()); - assertEquals(2000.0, backoff.getMaxBackoffMs()); - assertEquals(1.8, backoff.getGrowthFactor()); - - // First delay: 0-50ms - long delay1 = backoff.calculateDelayMs(); - assertTrue(delay1 >= 0 && delay1 <= 50, "First delay should be 0-50ms, got: " + delay1); - - // Second delay: 0-90ms (50 * 1.8) - long delay2 = backoff.calculateDelayMs(); - assertTrue(delay2 >= 0 && delay2 <= 90, "Second delay should be 0-90ms, got: " + delay2); - } - - @Test - void testReset() { - ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); - - // Perform some retries - backoff.calculateDelayMs(); - backoff.calculateDelayMs(); - assertEquals(2, backoff.getRetryCount()); - - // Reset and verify counter is back to 0 - backoff.reset(); - assertEquals(0, backoff.getRetryCount()); - - // First delay after reset should be in the initial range again - long delay = backoff.calculateDelayMs(); - assertTrue(delay >= 0 && delay <= 5, "First delay after reset should be 0-5ms, got: " + delay); - } - - @Test - void testCommandRetrySequenceMatchesSpec() { - // Test that command retry follows spec: 100ms * 2^i capped at 10000ms - ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); - - double[] expectedMaxValues = {100.0, 200.0, 400.0, 800.0, 1600.0, 3200.0, 6400.0, 10000.0, 10000.0}; - - for (int i = 0; i < expectedMaxValues.length; i++) { - long delay = backoff.calculateDelayMs(); - double expectedMax = expectedMaxValues[i]; - assertTrue(delay >= 0 && delay <= Math.round(expectedMax), String.format("Retry %d: delay should be 0-%d ms, got: %d", i, Math.round(expectedMax), delay)); - } - } - - // Tests for the test jitter supplier functionality - - @Test - void testJitterSupplierWithZeroJitter() { - // Set jitter to always return 0 (no backoff) - ExponentialBackoff.setTestJitterSupplier(() -> 0.0); - - ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); + void testTransactionRetryBackoffRespectsMaximum() { + ExponentialBackoff backoff = ExponentialBackoff.TRANSACTION; - // With jitter = 0, all delays should be 0 - for (int i = 0; i < 10; i++) { - long delay = backoff.calculateDelayMs(); - assertEquals(0, delay, "With jitter=0, delay should always be 0ms"); + // Even at high retry counts, delay should never exceed 500ms + for (int retry = 0; retry < 25; retry++) { + long delay = backoff.calculateDelayBeforeNextRetryMs(retry); + assertTrue(delay >= 0 && delay <= 500, String.format("Retry %d: delay should be capped at 500 ms, got: %d ms", retry, delay)); } } @Test - void testJitterSupplierWithFullJitter() { - // Set jitter to always return 1.0 (full backoff) - ExponentialBackoff.setTestJitterSupplier(() -> 1.0); - - ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); + void testCustomJitter() { + ExponentialBackoff backoff = ExponentialBackoff.TRANSACTION; // Expected delays with jitter=1.0 and growth factor 1.5 double[] expectedDelays = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; + double jitter = 1.0; - for (int i = 0; i < expectedDelays.length; i++) { - long delay = backoff.calculateDelayMs(); - long expected = Math.round(expectedDelays[i]); - assertEquals(expected, delay, String.format("Retry %d: with jitter=1.0, delay should be %dms", i, expected)); - } - } - - @Test - void testJitterSupplierWithHalfJitter() { - // Set jitter to always return 0.5 (half backoff) - ExponentialBackoff.setTestJitterSupplier(() -> 0.5); - - ExponentialBackoff backoff = ExponentialBackoff.forTransactionRetry(); - - // Expected delays with jitter=0.5 and growth factor 1.5 - double[] expectedMaxDelays = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; - - for (int i = 0; i < expectedMaxDelays.length; i++) { - long delay = backoff.calculateDelayMs(); - long expected = Math.round(0.5 * expectedMaxDelays[i]); - assertEquals(expected, delay, String.format("Retry %d: with jitter=0.5, delay should be %dms", i, expected)); + for (int retry = 0; retry < expectedDelays.length; retry++) { + long delay = backoff.calculateDelayBeforeNextRetryMs(retry, jitter); + long expected = Math.round(expectedDelays[retry]); + assertEquals(expected, delay, String.format("Retry %d: with jitter=1.0, delay should be %d ms", retry, expected)); } - } - - @Test - void testJitterSupplierForCommandRetry() { - // Test that custom jitter also works with command retry configuration - ExponentialBackoff.setTestJitterSupplier(() -> 1.0); - - ExponentialBackoff backoff = ExponentialBackoff.forCommandRetry(); - - // Expected first few delays with jitter=1.0 and growth factor 2.0 - long[] expectedDelays = {100, 200, 400, 800, 1600, 3200, 6400, 10000}; - - for (int i = 0; i < expectedDelays.length; i++) { - long delay = backoff.calculateDelayMs(); - assertEquals(expectedDelays[i], delay, String.format("Command retry %d: with jitter=1.0, delay should be %dms", i, expectedDelays[i])); - } - } - - @Test - void testClearingJitterSupplierReturnsToRandom() { - // First set a fixed jitter - ExponentialBackoff.setTestJitterSupplier(() -> 0.0); - ExponentialBackoff backoff1 = ExponentialBackoff.forTransactionRetry(); - long delay1 = backoff1.calculateDelayMs(); - assertEquals(0, delay1, "With jitter=0, delay should be 0ms"); - - // Clear the test jitter supplier - ExponentialBackoff.clearTestJitterSupplier(); - - // Now delays should be random again - ExponentialBackoff backoff2 = ExponentialBackoff.forTransactionRetry(); - - // Run multiple times to verify randomness (statistically very unlikely to get all zeros) - boolean foundNonZero = false; - for (int i = 0; i < 20; i++) { - long delay = backoff2.calculateDelayMs(); - assertTrue(delay >= 0 && delay <= Math.round(5.0 * Math.pow(1.5, i)), "Delay should be within expected range"); - if (delay > 0) { - foundNonZero = true; - } + // With jitter = 0, all delays should be 0 + jitter = 0; + for (int retry = 0; retry < 10; retry++) { + long delay = backoff.calculateDelayBeforeNextRetryMs(retry, jitter); + assertEquals(0, delay, "With jitter=0, delay should always be 0 ms"); } - assertTrue(foundNonZero, "After clearing test jitter, should get some non-zero delays (random behavior)"); - } - - @Test - void testJitterSupplierWithCustomBackoff() { - // Test that custom jitter works with custom backoff parameters - ExponentialBackoff.setTestJitterSupplier(() -> 0.75); - - ExponentialBackoff backoff = new ExponentialBackoff(100.0, 1000.0, 2.5); - - // First delay: 0.75 * 100 = 75 - assertEquals(75, backoff.calculateDelayMs()); - - // Second delay: 0.75 * 100 * 2.5 = 0.75 * 250 = 188 (rounded) - assertEquals(188, backoff.calculateDelayMs()); - - // Third delay: 0.75 * 100 * 2.5^2 = 0.75 * 625 = 469 (rounded) - assertEquals(469, backoff.calculateDelayMs()); } } diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index ddbc0ecf205..fcaea52aaa1 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -27,7 +27,6 @@ import com.mongodb.WriteConcern; import com.mongodb.client.ClientSession; import com.mongodb.client.TransactionBody; -import com.mongodb.internal.ExponentialBackoff; import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.observability.micrometer.TracingManager; import com.mongodb.internal.observability.micrometer.TransactionSpan; @@ -39,6 +38,7 @@ import com.mongodb.internal.operation.WriteOperation; import com.mongodb.internal.session.BaseClientSessionImpl; import com.mongodb.internal.session.ServerSessionPool; +import com.mongodb.internal.time.ExponentialBackoff; import com.mongodb.lang.Nullable; import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL; @@ -47,6 +47,7 @@ import static com.mongodb.assertions.Assertions.assertTrue; import static com.mongodb.assertions.Assertions.isTrue; import static com.mongodb.assertions.Assertions.notNull; +import static com.mongodb.internal.thread.InterruptionUtil.interruptAndCreateMongoInterruptedException; final class ClientSessionImpl extends BaseClientSessionImpl implements ClientSession { @@ -252,7 +253,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra notNull("transactionBody", transactionBody); long startTime = ClientSessionClock.INSTANCE.now(); TimeoutContext withTransactionTimeoutContext = createTimeoutContext(options); - ExponentialBackoff transactionBackoff = ExponentialBackoff.forTransactionRetry(); + ExponentialBackoff transactionBackoff = ExponentialBackoff.TRANSACTION; int transactionAttempt = 0; MongoException lastError = null; @@ -260,22 +261,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra outer: while (true) { if (transactionAttempt > 0) { - long backoffMs = transactionBackoff.calculateDelayMs(transactionAttempt - 1); - // Check if backoff would exceed timeout - if (ClientSessionClock.INSTANCE.now() + backoffMs - startTime >= MAX_RETRY_TIME_LIMIT_MS) { - if (lastError != null) { - throw lastError; - } - throw new MongoClientException("Transaction retry timeout exceeded"); - } - try { - if (backoffMs > 0) { - Thread.sleep(backoffMs); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new MongoClientException("Transaction retry interrupted", e); - } + backoff(transactionBackoff, transactionAttempt, startTime, lastError); } T retVal; try { @@ -387,4 +373,22 @@ private TimeoutContext createTimeoutContext(final TransactionOptions transaction TransactionOptions.merge(transactionOptions, getOptions().getDefaultTransactionOptions()), operationExecutor.getTimeoutSettings())); } + + private static void backoff(final ExponentialBackoff exponentialBackoff, final int transactionAttempt, final long startTime, + final MongoException lastError) { + long backoffMs = exponentialBackoff.calculateDelayBeforeNextRetryMs(transactionAttempt - 1); + if (ClientSessionClock.INSTANCE.now() + backoffMs - startTime >= MAX_RETRY_TIME_LIMIT_MS) { + if (lastError != null) { + throw lastError; + } + throw new MongoClientException("Transaction retry timeout exceeded"); + } + try { + if (backoffMs > 0) { + Thread.sleep(backoffMs); + } + } catch (InterruptedException e) { + throw interruptAndCreateMongoInterruptedException("Transaction retry interrupted", e); + } + } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 92ad8792c33..e2dce11583f 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -22,7 +22,8 @@ import com.mongodb.TransactionOptions; import com.mongodb.client.internal.ClientSessionClock; import com.mongodb.client.model.Sorts; -import com.mongodb.internal.ExponentialBackoff; +import com.mongodb.internal.time.ExponentialBackoff; +import org.bson.BsonDocument; import org.bson.Document; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -34,6 +35,7 @@ import static com.mongodb.ClusterFixture.TIMEOUT; import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet; import static com.mongodb.ClusterFixture.isSharded; +import static com.mongodb.client.Fixture.getPrimary; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -206,83 +208,76 @@ public void testTimeoutMSAndLegacySettings() { } } - // - // Test that exponential backoff is applied when retrying transactions - // Backoff uses growth factor of 1.5 as per spec - // - @Test - void testExponentialBackoffOnTransientError() { - // Configure failpoint to simulate transient errors - MongoDatabase failPointAdminDb = client.getDatabase("admin"); - failPointAdminDb.runCommand( - Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 3}, " - + "'data': {'failCommands': ['insert'], 'errorCode': 112, " - + "'errorLabels': ['TransientTransactionError']}}")); - - try (ClientSession session = client.startSession()) { - // Track retry count - AtomicInteger retryCount = new AtomicInteger(0); - - session.withTransaction(() -> { - retryCount.incrementAndGet(); // Count the attempt before the operation that might fail - return collection.insertOne(session, Document.parse("{ _id : 'backoff-test' }")); - }); - - assertEquals(4, retryCount.get(), "Expected 1 initial attempt + 3 retries"); - } finally { - failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); - } - } - - // - // Test that retries within withTransaction do not occur immediately - // This test verifies that exponential backoff is enforced during commit retries - // See: https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/tests/README.md#retry-backoff-is-enforced - // + /** + * See + * Convenient API Prose Tests. + */ @DisplayName("Retry Backoff is Enforced") @Test - void testRetryBackoffIsEnforced() { - MongoDatabase failPointAdminDb = client.getDatabase("admin"); - - // Test 1: Run with jitter = 0 (no backoff) + public void testRetryBackoffIsEnforced() throws InterruptedException { + // Run with jitter = 0 (no backoff) ExponentialBackoff.setTestJitterSupplier(() -> 0.0); - failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251}}")); + BsonDocument failPointDocument = BsonDocument.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " + + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251}}"); long noBackoffTime; - try (ClientSession session = client.startSession()) { + try (ClientSession session = client.startSession(); + FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { long startNanos = System.nanoTime(); - session.withTransaction(() -> collection.insertOne(session, Document.parse("{ _id : 'backoff-test-no-jitter' }"))); - noBackoffTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);``` + session.withTransaction(() -> collection.insertOne(session, Document.parse("{}"))); + noBackoffTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos); } finally { // Clear the test jitter supplier to avoid affecting other tests ExponentialBackoff.clearTestJitterSupplier(); - failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); } - // Test 2: Run with jitter = 1 (full backoff) + // Run with jitter = 1 (full backoff) ExponentialBackoff.setTestJitterSupplier(() -> 1.0); - failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251}}")); + failPointDocument = BsonDocument.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " + + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251}}"); long withBackoffTime; - try (ClientSession session = client.startSession()) { + try (ClientSession session = client.startSession(); + FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { long startNanos = System.nanoTime(); - session.withTransaction(() -> collection.insertOne(session, Document.parse("{ _id : 'backoff-test-full-jitter' }"))); + session.withTransaction(() -> collection.insertOne(session, Document.parse("{}"))); withBackoffTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos); } finally { ExponentialBackoff.clearTestJitterSupplier(); - failPointAdminDb.runCommand(Document.parse("{'configureFailPoint': 'failCommand', 'mode': 'off'}")); } - long expectedWithBackoffTime = noBackoffTime + 1800; // 1.8 seconds as per spec + long expectedWithBackoffTime = noBackoffTime + 1800; long actualDifference = Math.abs(withBackoffTime - expectedWithBackoffTime); - assertTrue(actualDifference < 1000, String.format("Expected withBackoffTime to be ~%dms (noBackoffTime %dms + 1800ms), but" - + " got %dms. Difference: %dms (tolerance: 1000ms per spec)", expectedWithBackoffTime, noBackoffTime, withBackoffTime, + assertTrue(actualDifference < 1000, String.format("Expected withBackoffTime to be ~% dms (noBackoffTime %d ms + 1800 ms), but" + + " got %d ms. Difference: %d ms (tolerance: 1000 ms per spec)", expectedWithBackoffTime, noBackoffTime, withBackoffTime, actualDifference)); } + /** + * This test is not from the specification. + */ + @Test + public void testExponentialBackoffOnTransientError() throws InterruptedException { + BsonDocument failPointDocument = BsonDocument.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 3}, " + + "'data': {'failCommands': ['insert'], 'errorCode': 112, " + + "'errorLabels': ['TransientTransactionError']}}"); + + try (ClientSession session = client.startSession(); + FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { + AtomicInteger attemptsCount = new AtomicInteger(0); + + session.withTransaction(() -> { + attemptsCount.incrementAndGet(); // Count the attempt before the operation that might fail + return collection.insertOne(session, Document.parse("{}")); + }); + + assertEquals(4, attemptsCount.get(), "Expected 1 initial attempt + 3 retries"); + } + } + private boolean canRunTests() { return isSharded() || isDiscoverableReplicaSet(); } From 36ecbf9d6236d7d133677c5e807519bf68270fa7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 16 Jan 2026 00:24:22 +0000 Subject: [PATCH 42/60] remove annotation --- .../src/main/com/mongodb/internal/time/ExponentialBackoff.java | 2 -- .../src/main/com/mongodb/client/internal/ClientSessionImpl.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java index 2a69e8ba9e2..ed9bba51d7f 100644 --- a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java +++ b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java @@ -16,7 +16,6 @@ package com.mongodb.internal.time; -import com.mongodb.annotations.NotThreadSafe; import com.mongodb.internal.VisibleForTesting; import java.util.concurrent.ThreadLocalRandom; @@ -27,7 +26,6 @@ /** * Implements exponential backoff with jitter for retry scenarios. */ -@NotThreadSafe public enum ExponentialBackoff { TRANSACTION(5.0, 500.0, 1.5); diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index fcaea52aaa1..03ef3248c36 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -277,7 +277,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra abortTransaction(); } if (e instanceof MongoException) { - lastError = (MongoException) e; // Store last error + lastError = (MongoException) e; if (!(e instanceof MongoOperationTimeoutException)) { MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e); if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL) From ad430958a6e73fb8498532f4b969da6287049e37 Mon Sep 17 00:00:00 2001 From: Almas Abdrazak Date: Wed, 21 Jan 2026 15:52:06 -0800 Subject: [PATCH 43/60] improve git clone for mongo tools repository (#1866) Pipeline doesn't clone the whole git history of driver tools --------- Co-authored-by: Almas Abdrazak --- .evergreen/.evg.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/.evg.yml b/.evergreen/.evg.yml index 968db1b3c97..525861928f3 100644 --- a/.evergreen/.evg.yml +++ b/.evergreen/.evg.yml @@ -112,7 +112,7 @@ functions: # If this was a patch build, doing a fresh clone would not actually test the patch cp -R ${PROJECT_DIRECTORY}/ $DRIVERS_TOOLS else - git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS + git clone --depth 1 https://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS fi echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config From b92207514a6bd3e7b6e2c4397b3ae8fce211ddfe Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Mon, 26 Jan 2026 10:07:09 +0000 Subject: [PATCH 44/60] Bson specification testing Moved specifications submodule out of driver-core into a testing directory. Deleted old copy of bson specification tests and updated to use the submodule Fixed Json output for positive exponents to match the the extended json specification Added test exceptions to BinaryVectorGenericBsonTest Added BsonBinaryVector prose tests Added extra regression tests to ensure both explicit and implicit doubles with positive exponets parse as expected. JAVA-5877 JAVA-5779 JAVA-5782 JAVA-5652 --- .gitmodules | 2 +- bson/build.gradle.kts | 5 + bson/src/main/org/bson/BinaryVector.java | 11 +- .../main/org/bson/Float32BinaryVector.java | 8 +- bson/src/main/org/bson/Int8BinaryVector.java | 6 +- .../main/org/bson/PackedBitBinaryVector.java | 13 +- .../json/ExtendedJsonDoubleConverter.java | 2 +- .../org/bson/json/JsonDoubleConverter.java | 2 +- .../main/org/bson/json/JsonDoubleHelper.java | 32 + .../RelaxedExtendedJsonDoubleConverter.java | 2 +- .../resources/bson-binary-vector/float32.json | 50 - .../resources/bson-binary-vector/int8.json | 56 - .../bson-binary-vector/packed_bit.json | 97 - bson/src/test/resources/bson/array.json | 49 - bson/src/test/resources/bson/binary.json | 153 -- bson/src/test/resources/bson/boolean.json | 27 - bson/src/test/resources/bson/code.json | 67 - .../src/test/resources/bson/code_w_scope.json | 78 - bson/src/test/resources/bson/datetime.json | 42 - bson/src/test/resources/bson/dbpointer.json | 56 - bson/src/test/resources/bson/dbref.json | 51 - .../src/test/resources/bson/decimal128-1.json | 341 ---- .../src/test/resources/bson/decimal128-2.json | 793 -------- .../src/test/resources/bson/decimal128-3.json | 1771 ----------------- .../src/test/resources/bson/decimal128-4.json | 165 -- .../src/test/resources/bson/decimal128-5.json | 402 ---- .../src/test/resources/bson/decimal128-6.json | 131 -- .../src/test/resources/bson/decimal128-7.json | 327 --- bson/src/test/resources/bson/document.json | 60 - bson/src/test/resources/bson/double.json | 87 - bson/src/test/resources/bson/int32.json | 43 - bson/src/test/resources/bson/int64.json | 43 - bson/src/test/resources/bson/maxkey.json | 12 - bson/src/test/resources/bson/minkey.json | 12 - .../resources/bson/multi-type-deprecated.json | 15 - bson/src/test/resources/bson/multi-type.json | 11 - bson/src/test/resources/bson/null.json | 12 - bson/src/test/resources/bson/oid.json | 28 - bson/src/test/resources/bson/regex.json | 65 - bson/src/test/resources/bson/string.json | 72 - bson/src/test/resources/bson/symbol.json | 80 - bson/src/test/resources/bson/timestamp.json | 34 - bson/src/test/resources/bson/top.json | 266 --- bson/src/test/resources/bson/undefined.json | 15 - bson/src/test/unit/org/bson/DocumentTest.java | 10 + .../test/unit/org/bson/GenericBsonTest.java | 2 +- .../unit/org/bson/json/JsonWriterTest.java | 10 +- .../BinaryVectorProseTest.java} | 97 +- ...ricBsonTest.java => BinaryVectorTest.java} | 110 +- driver-core/build.gradle.kts | 5 + driver-core/src/test/resources/specifications | 1 - .../ServerSelectionSelectionTest.java | 6 +- .../unified/UnifiedTestModifications.java | 8 +- .../resources/logback-test.xml | 0 testing/resources/specifications | 1 + 55 files changed, 241 insertions(+), 5603 deletions(-) create mode 100644 bson/src/main/org/bson/json/JsonDoubleHelper.java delete mode 100644 bson/src/test/resources/bson-binary-vector/float32.json delete mode 100644 bson/src/test/resources/bson-binary-vector/int8.json delete mode 100644 bson/src/test/resources/bson-binary-vector/packed_bit.json delete mode 100644 bson/src/test/resources/bson/array.json delete mode 100644 bson/src/test/resources/bson/binary.json delete mode 100644 bson/src/test/resources/bson/boolean.json delete mode 100644 bson/src/test/resources/bson/code.json delete mode 100644 bson/src/test/resources/bson/code_w_scope.json delete mode 100644 bson/src/test/resources/bson/datetime.json delete mode 100644 bson/src/test/resources/bson/dbpointer.json delete mode 100644 bson/src/test/resources/bson/dbref.json delete mode 100644 bson/src/test/resources/bson/decimal128-1.json delete mode 100644 bson/src/test/resources/bson/decimal128-2.json delete mode 100644 bson/src/test/resources/bson/decimal128-3.json delete mode 100644 bson/src/test/resources/bson/decimal128-4.json delete mode 100644 bson/src/test/resources/bson/decimal128-5.json delete mode 100644 bson/src/test/resources/bson/decimal128-6.json delete mode 100644 bson/src/test/resources/bson/decimal128-7.json delete mode 100644 bson/src/test/resources/bson/document.json delete mode 100644 bson/src/test/resources/bson/double.json delete mode 100644 bson/src/test/resources/bson/int32.json delete mode 100644 bson/src/test/resources/bson/int64.json delete mode 100644 bson/src/test/resources/bson/maxkey.json delete mode 100644 bson/src/test/resources/bson/minkey.json delete mode 100644 bson/src/test/resources/bson/multi-type-deprecated.json delete mode 100644 bson/src/test/resources/bson/multi-type.json delete mode 100644 bson/src/test/resources/bson/null.json delete mode 100644 bson/src/test/resources/bson/oid.json delete mode 100644 bson/src/test/resources/bson/regex.json delete mode 100644 bson/src/test/resources/bson/string.json delete mode 100644 bson/src/test/resources/bson/symbol.json delete mode 100644 bson/src/test/resources/bson/timestamp.json delete mode 100644 bson/src/test/resources/bson/top.json delete mode 100644 bson/src/test/resources/bson/undefined.json rename bson/src/test/unit/org/bson/{BinaryVectorTest.java => vector/BinaryVectorProseTest.java} (64%) rename bson/src/test/unit/org/bson/vector/{BinaryVectorGenericBsonTest.java => BinaryVectorTest.java} (78%) delete mode 160000 driver-core/src/test/resources/specifications rename {driver-core/src/test => testing}/resources/logback-test.xml (100%) create mode 160000 testing/resources/specifications diff --git a/.gitmodules b/.gitmodules index a9ac62f04bb..48b90d1ef71 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "specifications"] - path = driver-core/src/test/resources/specifications + path = testing/resources/specifications url = https://github.com/mongodb/specifications diff --git a/bson/build.gradle.kts b/bson/build.gradle.kts index fab3cdaacb5..851d5620cc3 100644 --- a/bson/build.gradle.kts +++ b/bson/build.gradle.kts @@ -25,6 +25,11 @@ plugins { base.archivesName.set("bson") +tasks.processTestResources { + from("${rootProject.projectDir}/testing/resources") + into("${layout.buildDirectory.get()}/resources/test") +} + configureMavenPublication { pom { name.set("BSON") diff --git a/bson/src/main/org/bson/BinaryVector.java b/bson/src/main/org/bson/BinaryVector.java index 273b4a0e5e9..a1914601a9d 100644 --- a/bson/src/main/org/bson/BinaryVector.java +++ b/bson/src/main/org/bson/BinaryVector.java @@ -18,9 +18,8 @@ import org.bson.annotations.Beta; import org.bson.annotations.Reason; - -import static org.bson.assertions.Assertions.isTrueArgument; -import static org.bson.assertions.Assertions.notNull; +import org.bson.diagnostics.Logger; +import org.bson.diagnostics.Loggers; /** * Binary Vectors are densely packed arrays of numbers, all the same type, which are stored and retrieved efficiently using the BSON Binary @@ -33,6 +32,7 @@ * @since 5.3 */ public abstract class BinaryVector { + protected static final Logger LOGGER = Loggers.getLogger("BinaryVector"); private final DataType dataType; BinaryVector(final DataType dataType) { @@ -64,9 +64,6 @@ public abstract class BinaryVector { */ @Beta(Reason.SERVER) 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); } @@ -83,7 +80,6 @@ public static PackedBitBinaryVector packedBitVector(final byte[] data, final byt * @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); } @@ -99,7 +95,6 @@ public static Int8BinaryVector int8Vector(final byte[] 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); } diff --git a/bson/src/main/org/bson/Float32BinaryVector.java b/bson/src/main/org/bson/Float32BinaryVector.java index 37d1b8abb6e..89179f6f307 100644 --- a/bson/src/main/org/bson/Float32BinaryVector.java +++ b/bson/src/main/org/bson/Float32BinaryVector.java @@ -18,7 +18,7 @@ import java.util.Arrays; -import static org.bson.assertions.Assertions.assertNotNull; +import static org.bson.assertions.Assertions.notNull; /** * Represents a vector of 32-bit floating-point numbers, where each element in the vector is a float. @@ -35,9 +35,9 @@ public final class Float32BinaryVector extends BinaryVector { private final float[] data; - Float32BinaryVector(final float[] vectorData) { + Float32BinaryVector(final float[] data) { super(DataType.FLOAT32); - this.data = assertNotNull(vectorData); + this.data = notNull("data", data); } /** @@ -49,7 +49,7 @@ public final class Float32BinaryVector extends BinaryVector { * @return the underlying float array representing this {@link Float32BinaryVector} vector. */ public float[] getData() { - return assertNotNull(data); + return data; } @Override diff --git a/bson/src/main/org/bson/Int8BinaryVector.java b/bson/src/main/org/bson/Int8BinaryVector.java index a851aff94ff..14b0803aa1c 100644 --- a/bson/src/main/org/bson/Int8BinaryVector.java +++ b/bson/src/main/org/bson/Int8BinaryVector.java @@ -19,7 +19,7 @@ import java.util.Arrays; import java.util.Objects; -import static org.bson.assertions.Assertions.assertNotNull; +import static org.bson.assertions.Assertions.notNull; /** * Represents a vector of 8-bit signed integers, where each element in the vector is a byte. @@ -38,7 +38,7 @@ public final class Int8BinaryVector extends BinaryVector { Int8BinaryVector(final byte[] data) { super(DataType.INT8); - this.data = assertNotNull(data); + this.data = notNull("data", data); } /** @@ -50,7 +50,7 @@ public final class Int8BinaryVector extends BinaryVector { * @return the underlying byte array representing this {@link Int8BinaryVector} vector. */ public byte[] getData() { - return assertNotNull(data); + return data; } @Override diff --git a/bson/src/main/org/bson/PackedBitBinaryVector.java b/bson/src/main/org/bson/PackedBitBinaryVector.java index 33200650204..a20155c7fb3 100644 --- a/bson/src/main/org/bson/PackedBitBinaryVector.java +++ b/bson/src/main/org/bson/PackedBitBinaryVector.java @@ -23,6 +23,8 @@ import java.util.Objects; import static org.bson.assertions.Assertions.assertNotNull; +import static org.bson.assertions.Assertions.isTrueArgument; +import static org.bson.assertions.Assertions.notNull; /** * Represents a packed bit vector, where each element of the vector is represented by a single bit (0 or 1). @@ -43,8 +45,17 @@ public final class PackedBitBinaryVector extends BinaryVector { PackedBitBinaryVector(final byte[] data, final byte padding) { super(DataType.PACKED_BIT); - this.data = assertNotNull(data); + this.data = notNull("data", data); this.padding = padding; + 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); + if (padding > 0) { + int mask = (1 << padding) - 1; + if ((data[data.length - 1] & mask) != 0) { + // JAVA-5848 in version 6.0.0 will convert this logging into an IllegalArgumentException + LOGGER.warn("The last " + padding + " padded bits should be zero in the final byte."); + } + } } /** diff --git a/bson/src/main/org/bson/json/ExtendedJsonDoubleConverter.java b/bson/src/main/org/bson/json/ExtendedJsonDoubleConverter.java index 1ad0db0ec1b..9f72dfc61d1 100644 --- a/bson/src/main/org/bson/json/ExtendedJsonDoubleConverter.java +++ b/bson/src/main/org/bson/json/ExtendedJsonDoubleConverter.java @@ -21,7 +21,7 @@ class ExtendedJsonDoubleConverter implements Converter { public void convert(final Double value, final StrictJsonWriter writer) { writer.writeStartObject(); writer.writeName("$numberDouble"); - writer.writeString(Double.toString(value)); + writer.writeString(JsonDoubleHelper.toString(value)); writer.writeEndObject(); } diff --git a/bson/src/main/org/bson/json/JsonDoubleConverter.java b/bson/src/main/org/bson/json/JsonDoubleConverter.java index 26b46ab89d5..0ded8de06ba 100644 --- a/bson/src/main/org/bson/json/JsonDoubleConverter.java +++ b/bson/src/main/org/bson/json/JsonDoubleConverter.java @@ -19,6 +19,6 @@ class JsonDoubleConverter implements Converter { @Override public void convert(final Double value, final StrictJsonWriter writer) { - writer.writeNumber(Double.toString(value)); + writer.writeNumber(JsonDoubleHelper.toString(value)); } } diff --git a/bson/src/main/org/bson/json/JsonDoubleHelper.java b/bson/src/main/org/bson/json/JsonDoubleHelper.java new file mode 100644 index 00000000000..ff6976beedd --- /dev/null +++ b/bson/src/main/org/bson/json/JsonDoubleHelper.java @@ -0,0 +1,32 @@ +/* + * 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.json; + +import java.util.regex.Pattern; + +final class JsonDoubleHelper { + + private static final Pattern POSITIVE_EXPONENT_PATTERN = Pattern.compile("E(\\d+)"); + private static final String POSITIVE_EXPONENT_REPLACER = "E+$1"; + + static String toString(final double value) { + String doubleString = Double.toString(value); + return POSITIVE_EXPONENT_PATTERN.matcher(doubleString).replaceAll(POSITIVE_EXPONENT_REPLACER); + } + + private JsonDoubleHelper() { + } +} diff --git a/bson/src/main/org/bson/json/RelaxedExtendedJsonDoubleConverter.java b/bson/src/main/org/bson/json/RelaxedExtendedJsonDoubleConverter.java index ac845b2ecd0..43627c555c3 100644 --- a/bson/src/main/org/bson/json/RelaxedExtendedJsonDoubleConverter.java +++ b/bson/src/main/org/bson/json/RelaxedExtendedJsonDoubleConverter.java @@ -24,7 +24,7 @@ public void convert(final Double value, final StrictJsonWriter writer) { if (value.isNaN() || value.isInfinite()) { FALLBACK_CONVERTER.convert(value, writer); } else { - writer.writeNumber(Double.toString(value)); + writer.writeNumber(JsonDoubleHelper.toString(value)); } } } diff --git a/bson/src/test/resources/bson-binary-vector/float32.json b/bson/src/test/resources/bson-binary-vector/float32.json deleted file mode 100644 index e1d142c184b..00000000000 --- a/bson/src/test/resources/bson-binary-vector/float32.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "description": "Tests of Binary subtype 9, Vectors, with dtype FLOAT32", - "test_key": "vector", - "tests": [ - { - "description": "Simple Vector FLOAT32", - "valid": true, - "vector": [127.0, 7.0], - "dtype_hex": "0x27", - "dtype_alias": "FLOAT32", - "padding": 0, - "canonical_bson": "1C00000005766563746F72000A0000000927000000FE420000E04000" - }, - { - "description": "Vector with decimals and negative value FLOAT32", - "valid": true, - "vector": [127.7, -7.7], - "dtype_hex": "0x27", - "dtype_alias": "FLOAT32", - "padding": 0, - "canonical_bson": "1C00000005766563746F72000A0000000927006666FF426666F6C000" - }, - { - "description": "Empty Vector FLOAT32", - "valid": true, - "vector": [], - "dtype_hex": "0x27", - "dtype_alias": "FLOAT32", - "padding": 0, - "canonical_bson": "1400000005766563746F72000200000009270000" - }, - { - "description": "Infinity Vector FLOAT32", - "valid": true, - "vector": ["-inf", 0.0, "inf"], - "dtype_hex": "0x27", - "dtype_alias": "FLOAT32", - "padding": 0, - "canonical_bson": "2000000005766563746F72000E000000092700000080FF000000000000807F00" - }, - { - "description": "FLOAT32 with padding", - "valid": false, - "vector": [127.0, 7.0], - "dtype_hex": "0x27", - "dtype_alias": "FLOAT32", - "padding": 3 - } - ] -} \ No newline at end of file diff --git a/bson/src/test/resources/bson-binary-vector/int8.json b/bson/src/test/resources/bson-binary-vector/int8.json deleted file mode 100644 index c10c1b7d4e2..00000000000 --- a/bson/src/test/resources/bson-binary-vector/int8.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "description": "Tests of Binary subtype 9, Vectors, with dtype INT8", - "test_key": "vector", - "tests": [ - { - "description": "Simple Vector INT8", - "valid": true, - "vector": [127, 7], - "dtype_hex": "0x03", - "dtype_alias": "INT8", - "padding": 0, - "canonical_bson": "1600000005766563746F7200040000000903007F0700" - }, - { - "description": "Empty Vector INT8", - "valid": true, - "vector": [], - "dtype_hex": "0x03", - "dtype_alias": "INT8", - "padding": 0, - "canonical_bson": "1400000005766563746F72000200000009030000" - }, - { - "description": "Overflow Vector INT8", - "valid": false, - "vector": [128], - "dtype_hex": "0x03", - "dtype_alias": "INT8", - "padding": 0 - }, - { - "description": "Underflow Vector INT8", - "valid": false, - "vector": [-129], - "dtype_hex": "0x03", - "dtype_alias": "INT8", - "padding": 0 - }, - { - "description": "INT8 with padding", - "valid": false, - "vector": [127, 7], - "dtype_hex": "0x03", - "dtype_alias": "INT8", - "padding": 3 - }, - { - "description": "INT8 with float inputs", - "valid": false, - "vector": [127.77, 7.77], - "dtype_hex": "0x03", - "dtype_alias": "INT8", - "padding": 0 - } - ] -} \ No newline at end of file diff --git a/bson/src/test/resources/bson-binary-vector/packed_bit.json b/bson/src/test/resources/bson-binary-vector/packed_bit.json deleted file mode 100644 index 69fb3948335..00000000000 --- a/bson/src/test/resources/bson-binary-vector/packed_bit.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "description": "Tests of Binary subtype 9, Vectors, with dtype PACKED_BIT", - "test_key": "vector", - "tests": [ - { - "description": "Padding specified with no vector data PACKED_BIT", - "valid": false, - "vector": [], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 1 - }, - { - "description": "Simple Vector PACKED_BIT", - "valid": true, - "vector": [127, 7], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 0, - "canonical_bson": "1600000005766563746F7200040000000910007F0700" - }, - { - "description": "Empty Vector PACKED_BIT", - "valid": true, - "vector": [], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 0, - "canonical_bson": "1400000005766563746F72000200000009100000" - }, - { - "description": "PACKED_BIT with padding", - "valid": true, - "vector": [127, 7], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 3, - "canonical_bson": "1600000005766563746F7200040000000910037F0700" - }, - { - "description": "Overflow Vector PACKED_BIT", - "valid": false, - "vector": [256], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 0 - }, - { - "description": "Underflow Vector PACKED_BIT", - "valid": false, - "vector": [-1], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 0 - }, - { - "description": "Vector with float values PACKED_BIT", - "valid": false, - "vector": [127.5], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 0 - }, - { - "description": "Padding specified with no vector data PACKED_BIT", - "valid": false, - "vector": [], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 1 - }, - { - "description": "Exceeding maximum padding PACKED_BIT", - "valid": false, - "vector": [1], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 8 - }, - { - "description": "Negative padding PACKED_BIT", - "valid": false, - "vector": [1], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": -1 - }, - { - "description": "Vector with float values PACKED_BIT", - "valid": false, - "vector": [127.5], - "dtype_hex": "0x10", - "dtype_alias": "PACKED_BIT", - "padding": 0 - } - ] -} \ No newline at end of file diff --git a/bson/src/test/resources/bson/array.json b/bson/src/test/resources/bson/array.json deleted file mode 100644 index 9ff953e5ae7..00000000000 --- a/bson/src/test/resources/bson/array.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "description": "Array", - "bson_type": "0x04", - "test_key": "a", - "valid": [ - { - "description": "Empty", - "canonical_bson": "0D000000046100050000000000", - "canonical_extjson": "{\"a\" : []}" - }, - { - "description": "Single Element Array", - "canonical_bson": "140000000461000C0000001030000A0000000000", - "canonical_extjson": "{\"a\" : [{\"$numberInt\": \"10\"}]}" - }, - { - "description": "Single Element Array with index set incorrectly to empty string", - "degenerate_bson": "130000000461000B00000010000A0000000000", - "canonical_bson": "140000000461000C0000001030000A0000000000", - "canonical_extjson": "{\"a\" : [{\"$numberInt\": \"10\"}]}" - }, - { - "description": "Single Element Array with index set incorrectly to ab", - "degenerate_bson": "150000000461000D000000106162000A0000000000", - "canonical_bson": "140000000461000C0000001030000A0000000000", - "canonical_extjson": "{\"a\" : [{\"$numberInt\": \"10\"}]}" - }, - { - "description": "Multi Element Array with duplicate indexes", - "degenerate_bson": "1b000000046100130000001030000a000000103000140000000000", - "canonical_bson": "1b000000046100130000001030000a000000103100140000000000", - "canonical_extjson": "{\"a\" : [{\"$numberInt\": \"10\"}, {\"$numberInt\": \"20\"}]}" - } - ], - "decodeErrors": [ - { - "description": "Array length too long: eats outer terminator", - "bson": "140000000461000D0000001030000A0000000000" - }, - { - "description": "Array length too short: leaks terminator", - "bson": "140000000461000B0000001030000A0000000000" - }, - { - "description": "Invalid Array: bad string length in field", - "bson": "1A00000004666F6F00100000000230000500000062617A000000" - } - ] -} diff --git a/bson/src/test/resources/bson/binary.json b/bson/src/test/resources/bson/binary.json deleted file mode 100644 index 0e0056f3a2c..00000000000 --- a/bson/src/test/resources/bson/binary.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "description": "Binary type", - "bson_type": "0x05", - "test_key": "x", - "valid": [ - { - "description": "subtype 0x00 (Zero-length)", - "canonical_bson": "0D000000057800000000000000", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"\", \"subType\" : \"00\"}}}" - }, - { - "description": "subtype 0x00 (Zero-length, keys reversed)", - "canonical_bson": "0D000000057800000000000000", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"\", \"subType\" : \"00\"}}}", - "degenerate_extjson": "{\"x\" : { \"$binary\" : {\"subType\" : \"00\", \"base64\" : \"\"}}}" - }, - { - "description": "subtype 0x00", - "canonical_bson": "0F0000000578000200000000FFFF00", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"//8=\", \"subType\" : \"00\"}}}" - }, - { - "description": "subtype 0x01", - "canonical_bson": "0F0000000578000200000001FFFF00", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"//8=\", \"subType\" : \"01\"}}}" - }, - { - "description": "subtype 0x02", - "canonical_bson": "13000000057800060000000202000000FFFF00", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"//8=\", \"subType\" : \"02\"}}}" - }, - { - "description": "subtype 0x03", - "canonical_bson": "1D000000057800100000000373FFD26444B34C6990E8E7D1DFC035D400", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"03\"}}}" - }, - { - "description": "subtype 0x04", - "canonical_bson": "1D000000057800100000000473FFD26444B34C6990E8E7D1DFC035D400", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"04\"}}}" - }, - { - "description": "subtype 0x04 UUID", - "canonical_bson": "1D000000057800100000000473FFD26444B34C6990E8E7D1DFC035D400", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"04\"}}}", - "degenerate_extjson": "{\"x\" : { \"$uuid\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035d4\"}}" - }, - { - "description": "subtype 0x05", - "canonical_bson": "1D000000057800100000000573FFD26444B34C6990E8E7D1DFC035D400", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"05\"}}}" - }, - { - "description": "subtype 0x07", - "canonical_bson": "1D000000057800100000000773FFD26444B34C6990E8E7D1DFC035D400", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"07\"}}}" - }, - { - "description": "subtype 0x08", - "canonical_bson": "1D000000057800100000000873FFD26444B34C6990E8E7D1DFC035D400", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"08\"}}}" - }, - { - "description": "subtype 0x80", - "canonical_bson": "0F0000000578000200000080FFFF00", - "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"//8=\", \"subType\" : \"80\"}}}" - }, - { - "description": "$type query operator (conflicts with legacy $binary form with $type field)", - "canonical_bson": "1F000000037800170000000224747970650007000000737472696E67000000", - "canonical_extjson": "{\"x\" : { \"$type\" : \"string\"}}" - }, - { - "description": "$type query operator (conflicts with legacy $binary form with $type field)", - "canonical_bson": "180000000378001000000010247479706500020000000000", - "canonical_extjson": "{\"x\" : { \"$type\" : {\"$numberInt\": \"2\"}}}" - }, - { - "description": "subtype 0x09 Vector FLOAT32", - "canonical_bson": "170000000578000A0000000927000000FE420000E04000", - "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"JwAAAP5CAADgQA==\", \"subType\": \"09\"}}}" - }, - { - "description": "subtype 0x09 Vector INT8", - "canonical_bson": "11000000057800040000000903007F0700", - "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"AwB/Bw==\", \"subType\": \"09\"}}}" - }, - { - "description": "subtype 0x09 Vector PACKED_BIT", - "canonical_bson": "11000000057800040000000910007F0700", - "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"EAB/Bw==\", \"subType\": \"09\"}}}" - }, - { - "description": "subtype 0x09 Vector (Zero-length) FLOAT32", - "canonical_bson": "0F0000000578000200000009270000", - "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"JwA=\", \"subType\": \"09\"}}}" - }, - { - "description": "subtype 0x09 Vector (Zero-length) INT8", - "canonical_bson": "0F0000000578000200000009030000", - "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"AwA=\", \"subType\": \"09\"}}}" - }, - { - "description": "subtype 0x09 Vector (Zero-length) PACKED_BIT", - "canonical_bson": "0F0000000578000200000009100000", - "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"EAA=\", \"subType\": \"09\"}}}" - } - ], - "decodeErrors": [ - { - "description": "Length longer than document", - "bson": "1D000000057800FF0000000573FFD26444B34C6990E8E7D1DFC035D400" - }, - { - "description": "Negative length", - "bson": "0D000000057800FFFFFFFF0000" - }, - { - "description": "subtype 0x02 length too long ", - "bson": "13000000057800060000000203000000FFFF00" - }, - { - "description": "subtype 0x02 length too short", - "bson": "13000000057800060000000201000000FFFF00" - }, - { - "description": "subtype 0x02 length negative one", - "bson": "130000000578000600000002FFFFFFFFFFFF00" - } - ], - "parseErrors": [ - { - "description": "$uuid wrong type", - "string": "{\"x\" : { \"$uuid\" : { \"data\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035d4\"}}}" - }, - { - "description": "$uuid invalid value--too short", - "string": "{\"x\" : { \"$uuid\" : \"73ffd264-44b3-90e8-e7d1dfc035d4\"}}" - }, - { - "description": "$uuid invalid value--too long", - "string": "{\"x\" : { \"$uuid\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035d4-789e4\"}}" - }, - { - "description": "$uuid invalid value--misplaced hyphens", - "string": "{\"x\" : { \"$uuid\" : \"73ff-d26444b-34c6-990e8e-7d1dfc035d4\"}}" - }, - { - "description": "$uuid invalid value--too many hyphens", - "string": "{\"x\" : { \"$uuid\" : \"----d264-44b3-4--9-90e8-e7d1dfc0----\"}}" - } - ] -} diff --git a/bson/src/test/resources/bson/boolean.json b/bson/src/test/resources/bson/boolean.json deleted file mode 100644 index 84c282299a1..00000000000 --- a/bson/src/test/resources/bson/boolean.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "description": "Boolean", - "bson_type": "0x08", - "test_key": "b", - "valid": [ - { - "description": "True", - "canonical_bson": "090000000862000100", - "canonical_extjson": "{\"b\" : true}" - }, - { - "description": "False", - "canonical_bson": "090000000862000000", - "canonical_extjson": "{\"b\" : false}" - } - ], - "decodeErrors": [ - { - "description": "Invalid boolean value of 2", - "bson": "090000000862000200" - }, - { - "description": "Invalid boolean value of -1", - "bson": "09000000086200FF00" - } - ] -} diff --git a/bson/src/test/resources/bson/code.json b/bson/src/test/resources/bson/code.json deleted file mode 100644 index b8482b2541b..00000000000 --- a/bson/src/test/resources/bson/code.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "description": "Javascript Code", - "bson_type": "0x0D", - "test_key": "a", - "valid": [ - { - "description": "Empty string", - "canonical_bson": "0D0000000D6100010000000000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"\"}}" - }, - { - "description": "Single character", - "canonical_bson": "0E0000000D610002000000620000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"b\"}}" - }, - { - "description": "Multi-character", - "canonical_bson": "190000000D61000D0000006162616261626162616261620000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"abababababab\"}}" - }, - { - "description": "two-byte UTF-8 (\u00e9)", - "canonical_bson": "190000000D61000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\"}}" - }, - { - "description": "three-byte UTF-8 (\u2606)", - "canonical_bson": "190000000D61000D000000E29886E29886E29886E298860000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"\\u2606\\u2606\\u2606\\u2606\"}}" - }, - { - "description": "Embedded nulls", - "canonical_bson": "190000000D61000D0000006162006261620062616261620000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"ab\\u0000bab\\u0000babab\"}}" - } - ], - "decodeErrors": [ - { - "description": "bad code string length: 0 (but no 0x00 either)", - "bson": "0C0000000D61000000000000" - }, - { - "description": "bad code string length: -1", - "bson": "0C0000000D6100FFFFFFFF00" - }, - { - "description": "bad code string length: eats terminator", - "bson": "100000000D6100050000006200620000" - }, - { - "description": "bad code string length: longer than rest of document", - "bson": "120000000D00FFFFFF00666F6F6261720000" - }, - { - "description": "code string is not null-terminated", - "bson": "100000000D610004000000616263FF00" - }, - { - "description": "empty code string, but extra null", - "bson": "0E0000000D610001000000000000" - }, - { - "description": "invalid UTF-8", - "bson": "0E0000000D610002000000E90000" - } - ] -} diff --git a/bson/src/test/resources/bson/code_w_scope.json b/bson/src/test/resources/bson/code_w_scope.json deleted file mode 100644 index f956bcd54f6..00000000000 --- a/bson/src/test/resources/bson/code_w_scope.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "description": "Javascript Code with Scope", - "bson_type": "0x0F", - "test_key": "a", - "valid": [ - { - "description": "Empty code string, empty scope", - "canonical_bson": "160000000F61000E0000000100000000050000000000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"\", \"$scope\" : {}}}" - }, - { - "description": "Non-empty code string, empty scope", - "canonical_bson": "1A0000000F610012000000050000006162636400050000000000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"abcd\", \"$scope\" : {}}}" - }, - { - "description": "Empty code string, non-empty scope", - "canonical_bson": "1D0000000F61001500000001000000000C000000107800010000000000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"\", \"$scope\" : {\"x\" : {\"$numberInt\": \"1\"}}}}" - }, - { - "description": "Non-empty code string and non-empty scope", - "canonical_bson": "210000000F6100190000000500000061626364000C000000107800010000000000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"abcd\", \"$scope\" : {\"x\" : {\"$numberInt\": \"1\"}}}}" - }, - { - "description": "Unicode and embedded null in code string, empty scope", - "canonical_bson": "1A0000000F61001200000005000000C3A9006400050000000000", - "canonical_extjson": "{\"a\" : {\"$code\" : \"\\u00e9\\u0000d\", \"$scope\" : {}}}" - } - ], - "decodeErrors": [ - { - "description": "field length zero", - "bson": "280000000F6100000000000500000061626364001300000010780001000000107900010000000000" - }, - { - "description": "field length negative", - "bson": "280000000F6100FFFFFFFF0500000061626364001300000010780001000000107900010000000000" - }, - { - "description": "field length too short (less than minimum size)", - "bson": "160000000F61000D0000000100000000050000000000" - }, - { - "description": "field length too short (truncates scope)", - "bson": "280000000F61001F0000000500000061626364001300000010780001000000107900010000000000" - }, - { - "description": "field length too long (clips outer doc)", - "bson": "280000000F6100210000000500000061626364001300000010780001000000107900010000000000" - }, - { - "description": "field length too long (longer than outer doc)", - "bson": "280000000F6100FF0000000500000061626364001300000010780001000000107900010000000000" - }, - { - "description": "bad code string: length too short", - "bson": "280000000F6100200000000400000061626364001300000010780001000000107900010000000000" - }, - { - "description": "bad code string: length too long (clips scope)", - "bson": "280000000F6100200000000600000061626364001300000010780001000000107900010000000000" - }, - { - "description": "bad code string: negative length", - "bson": "280000000F610020000000FFFFFFFF61626364001300000010780001000000107900010000000000" - }, - { - "description": "bad code string: length longer than field", - "bson": "280000000F610020000000FF00000061626364001300000010780001000000107900010000000000" - }, - { - "description": "bad scope doc (field has bad string length)", - "bson": "1C0000000F001500000001000000000C000000020000000000000000" - } - ] -} diff --git a/bson/src/test/resources/bson/datetime.json b/bson/src/test/resources/bson/datetime.json deleted file mode 100644 index f857afdc367..00000000000 --- a/bson/src/test/resources/bson/datetime.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "description": "DateTime", - "bson_type": "0x09", - "test_key": "a", - "valid": [ - { - "description": "epoch", - "canonical_bson": "10000000096100000000000000000000", - "relaxed_extjson": "{\"a\" : {\"$date\" : \"1970-01-01T00:00:00Z\"}}", - "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"0\"}}}" - }, - { - "description": "positive ms", - "canonical_bson": "10000000096100C5D8D6CC3B01000000", - "relaxed_extjson": "{\"a\" : {\"$date\" : \"2012-12-24T12:15:30.501Z\"}}", - "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"1356351330501\"}}}" - }, - { - "description": "negative", - "canonical_bson": "10000000096100C33CE7B9BDFFFFFF00", - "relaxed_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"-284643869501\"}}}", - "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"-284643869501\"}}}" - }, - { - "description" : "Y10K", - "canonical_bson" : "1000000009610000DC1FD277E6000000", - "canonical_extjson" : "{\"a\":{\"$date\":{\"$numberLong\":\"253402300800000\"}}}" - }, - { - "description": "leading zero ms", - "canonical_bson": "10000000096100D1D6D6CC3B01000000", - "relaxed_extjson": "{\"a\" : {\"$date\" : \"2012-12-24T12:15:30.001Z\"}}", - "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"1356351330001\"}}}" - } - ], - "decodeErrors": [ - { - "description": "datetime field truncated", - "bson": "0C0000000961001234567800" - } - ] -} diff --git a/bson/src/test/resources/bson/dbpointer.json b/bson/src/test/resources/bson/dbpointer.json deleted file mode 100644 index 377e556a0ad..00000000000 --- a/bson/src/test/resources/bson/dbpointer.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "description": "DBPointer type (deprecated)", - "bson_type": "0x0C", - "deprecated": true, - "test_key": "a", - "valid": [ - { - "description": "DBpointer", - "canonical_bson": "1A0000000C610002000000620056E1FC72E0C917E9C471416100", - "canonical_extjson": "{\"a\": {\"$dbPointer\": {\"$ref\": \"b\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}}", - "converted_bson": "2a00000003610022000000022472656600020000006200072469640056e1fc72e0c917e9c47141610000", - "converted_extjson": "{\"a\": {\"$ref\": \"b\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}" - }, - { - "description": "DBpointer with opposite key order", - "canonical_bson": "1A0000000C610002000000620056E1FC72E0C917E9C471416100", - "canonical_extjson": "{\"a\": {\"$dbPointer\": {\"$ref\": \"b\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}}", - "degenerate_extjson": "{\"a\": {\"$dbPointer\": {\"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}, \"$ref\": \"b\"}}}", - "converted_bson": "2a00000003610022000000022472656600020000006200072469640056e1fc72e0c917e9c47141610000", - "converted_extjson": "{\"a\": {\"$ref\": \"b\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}" - }, - { - "description": "With two-byte UTF-8", - "canonical_bson": "1B0000000C610003000000C3A90056E1FC72E0C917E9C471416100", - "canonical_extjson": "{\"a\": {\"$dbPointer\": {\"$ref\": \"é\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}}", - "converted_bson": "2B0000000361002300000002247265660003000000C3A900072469640056E1FC72E0C917E9C47141610000", - "converted_extjson": "{\"a\": {\"$ref\": \"é\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}" - } - ], - "decodeErrors": [ - { - "description": "String with negative length", - "bson": "1A0000000C6100FFFFFFFF620056E1FC72E0C917E9C471416100" - }, - { - "description": "String with zero length", - "bson": "1A0000000C610000000000620056E1FC72E0C917E9C471416100" - }, - { - "description": "String not null terminated", - "bson": "1A0000000C610002000000626256E1FC72E0C917E9C471416100" - }, - { - "description": "short OID (less than minimum length for field)", - "bson": "160000000C61000300000061620056E1FC72E0C91700" - }, - { - "description": "short OID (greater than minimum, but truncated)", - "bson": "1A0000000C61000300000061620056E1FC72E0C917E9C4716100" - }, - { - "description": "String with bad UTF-8", - "bson": "1A0000000C610002000000E90056E1FC72E0C917E9C471416100" - } - ] -} diff --git a/bson/src/test/resources/bson/dbref.json b/bson/src/test/resources/bson/dbref.json deleted file mode 100644 index 41c0b09d0ea..00000000000 --- a/bson/src/test/resources/bson/dbref.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "description": "Document type (DBRef sub-documents)", - "bson_type": "0x03", - "valid": [ - { - "description": "DBRef", - "canonical_bson": "37000000036462726566002b0000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e0000", - "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}}}" - }, - { - "description": "DBRef with database", - "canonical_bson": "4300000003646272656600370000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e0224646200030000006462000000", - "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}, \"$db\": \"db\"}}" - }, - { - "description": "DBRef with database and additional fields", - "canonical_bson": "48000000036462726566003c0000000224726566000b000000636f6c6c656374696f6e0010246964002a00000002246462000300000064620002666f6f0004000000626172000000", - "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$numberInt\": \"42\"}, \"$db\": \"db\", \"foo\": \"bar\"}}" - }, - { - "description": "DBRef with additional fields", - "canonical_bson": "4400000003646272656600380000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e02666f6f0004000000626172000000", - "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}, \"foo\": \"bar\"}}" - }, - { - "description": "Document with key names similar to those of a DBRef", - "canonical_bson": "3e0000000224726566000c0000006e6f742d612d646272656600072469640058921b3e6e32ab156a22b59e022462616e616e6100050000007065656c0000", - "canonical_extjson": "{\"$ref\": \"not-a-dbref\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}, \"$banana\": \"peel\"}" - }, - { - "description": "DBRef with additional dollar-prefixed and dotted fields", - "canonical_bson": "48000000036462726566003c0000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e10612e62000100000010246300010000000000", - "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}, \"a.b\": {\"$numberInt\": \"1\"}, \"$c\": {\"$numberInt\": \"1\"}}}" - }, - { - "description": "Sub-document resembles DBRef but $id is missing", - "canonical_bson": "26000000036462726566001a0000000224726566000b000000636f6c6c656374696f6e000000", - "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\"}}" - }, - { - "description": "Sub-document resembles DBRef but $ref is not a string", - "canonical_bson": "2c000000036462726566002000000010247265660001000000072469640058921b3e6e32ab156a22b59e0000", - "canonical_extjson": "{\"dbref\": {\"$ref\": {\"$numberInt\": \"1\"}, \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}}}" - }, - { - "description": "Sub-document resembles DBRef but $db is not a string", - "canonical_bson": "4000000003646272656600340000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e1024646200010000000000", - "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}, \"$db\": {\"$numberInt\": \"1\"}}}" - } - ] -} diff --git a/bson/src/test/resources/bson/decimal128-1.json b/bson/src/test/resources/bson/decimal128-1.json deleted file mode 100644 index 8e7fbc93c6f..00000000000 --- a/bson/src/test/resources/bson/decimal128-1.json +++ /dev/null @@ -1,341 +0,0 @@ -{ - "description": "Decimal128", - "bson_type": "0x13", - "test_key": "d", - "valid": [ - { - "description": "Special - Canonical NaN", - "canonical_bson": "180000001364000000000000000000000000000000007C00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}" - }, - { - "description": "Special - Negative NaN", - "canonical_bson": "18000000136400000000000000000000000000000000FC00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", - "lossy": true - }, - { - "description": "Special - Negative NaN", - "canonical_bson": "18000000136400000000000000000000000000000000FC00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-NaN\"}}", - "lossy": true - }, - { - "description": "Special - Canonical SNaN", - "canonical_bson": "180000001364000000000000000000000000000000007E00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", - "lossy": true - }, - { - "description": "Special - Negative SNaN", - "canonical_bson": "18000000136400000000000000000000000000000000FE00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", - "lossy": true - }, - { - "description": "Special - NaN with a payload", - "canonical_bson": "180000001364001200000000000000000000000000007E00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", - "lossy": true - }, - { - "description": "Special - Canonical Positive Infinity", - "canonical_bson": "180000001364000000000000000000000000000000007800", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" - }, - { - "description": "Special - Canonical Negative Infinity", - "canonical_bson": "18000000136400000000000000000000000000000000F800", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" - }, - { - "description": "Special - Invalid representation treated as 0", - "canonical_bson": "180000001364000000000000000000000000000000106C00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}", - "lossy": true - }, - { - "description": "Special - Invalid representation treated as -0", - "canonical_bson": "18000000136400DCBA9876543210DEADBEEF00000010EC00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}", - "lossy": true - }, - { - "description": "Special - Invalid representation treated as 0E3", - "canonical_bson": "18000000136400FFFFFFFFFFFFFFFFFFFFFFFFFFFF116C00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}", - "lossy": true - }, - { - "description": "Regular - Adjusted Exponent Limit", - "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CF22F00", - "canonical_extjson": "{\"d\": { \"$numberDecimal\": \"0.000001234567890123456789012345678901234\" }}" - }, - { - "description": "Regular - Smallest", - "canonical_bson": "18000000136400D204000000000000000000000000343000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.001234\"}}" - }, - { - "description": "Regular - Smallest with Trailing Zeros", - "canonical_bson": "1800000013640040EF5A07000000000000000000002A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00123400000\"}}" - }, - { - "description": "Regular - 0.1", - "canonical_bson": "1800000013640001000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1\"}}" - }, - { - "description": "Regular - 0.1234567890123456789012345678901234", - "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CFC2F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1234567890123456789012345678901234\"}}" - }, - { - "description": "Regular - 0", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "Regular - -0", - "canonical_bson": "18000000136400000000000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" - }, - { - "description": "Regular - -0.0", - "canonical_bson": "1800000013640000000000000000000000000000003EB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0\"}}" - }, - { - "description": "Regular - 2", - "canonical_bson": "180000001364000200000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2\"}}" - }, - { - "description": "Regular - 2.000", - "canonical_bson": "18000000136400D0070000000000000000000000003A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2.000\"}}" - }, - { - "description": "Regular - Largest", - "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3C403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1234567890123456789012345678901234\"}}" - }, - { - "description": "Scientific - Tiniest", - "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09ED010000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9.999999999999999999999999999999999E-6143\"}}" - }, - { - "description": "Scientific - Tiny", - "canonical_bson": "180000001364000100000000000000000000000000000000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" - }, - { - "description": "Scientific - Negative Tiny", - "canonical_bson": "180000001364000100000000000000000000000000008000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6176\"}}" - }, - { - "description": "Scientific - Adjusted Exponent Limit", - "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CF02F00", - "canonical_extjson": "{\"d\": { \"$numberDecimal\": \"1.234567890123456789012345678901234E-7\" }}" - }, - { - "description": "Scientific - Fractional", - "canonical_bson": "1800000013640064000000000000000000000000002CB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.00E-8\"}}" - }, - { - "description": "Scientific - 0 with Exponent", - "canonical_bson": "180000001364000000000000000000000000000000205F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6000\"}}" - }, - { - "description": "Scientific - 0 with Negative Exponent", - "canonical_bson": "1800000013640000000000000000000000000000007A2B00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-611\"}}" - }, - { - "description": "Scientific - No Decimal with Signed Exponent", - "canonical_bson": "180000001364000100000000000000000000000000463000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+3\"}}" - }, - { - "description": "Scientific - Trailing Zero", - "canonical_bson": "180000001364001A04000000000000000000000000423000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.050E+4\"}}" - }, - { - "description": "Scientific - With Decimal", - "canonical_bson": "180000001364006900000000000000000000000000423000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.05E+3\"}}" - }, - { - "description": "Scientific - Full", - "canonical_bson": "18000000136400FFFFFFFFFFFFFFFFFFFFFFFFFFFF403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"5192296858534827628530496329220095\"}}" - }, - { - "description": "Scientific - Large", - "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+6144\"}}" - }, - { - "description": "Scientific - Largest", - "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09EDFF5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9.999999999999999999999999999999999E+6144\"}}" - }, - { - "description": "Non-Canonical Parsing - Exponent Normalization", - "canonical_bson": "1800000013640064000000000000000000000000002CB000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-100E-10\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.00E-8\"}}" - }, - { - "description": "Non-Canonical Parsing - Unsigned Positive Exponent", - "canonical_bson": "180000001364000100000000000000000000000000463000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+3\"}}" - }, - { - "description": "Non-Canonical Parsing - Lowercase Exponent Identifier", - "canonical_bson": "180000001364000100000000000000000000000000463000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e+3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+3\"}}" - }, - { - "description": "Non-Canonical Parsing - Long Significand with Exponent", - "canonical_bson": "1800000013640079D9E0F9763ADA429D0200000000583000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12345689012345789012345E+12\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.2345689012345789012345E+34\"}}" - }, - { - "description": "Non-Canonical Parsing - Positive Sign", - "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3C403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+1234567890123456789012345678901234\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1234567890123456789012345678901234\"}}" - }, - { - "description": "Non-Canonical Parsing - Long Decimal String", - "canonical_bson": "180000001364000100000000000000000000000000722800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \".000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-999\"}}" - }, - { - "description": "Non-Canonical Parsing - nan", - "canonical_bson": "180000001364000000000000000000000000000000007C00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"nan\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}" - }, - { - "description": "Non-Canonical Parsing - nAn", - "canonical_bson": "180000001364000000000000000000000000000000007C00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"nAn\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}" - }, - { - "description": "Non-Canonical Parsing - +infinity", - "canonical_bson": "180000001364000000000000000000000000000000007800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+infinity\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" - }, - { - "description": "Non-Canonical Parsing - infinity", - "canonical_bson": "180000001364000000000000000000000000000000007800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"infinity\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" - }, - { - "description": "Non-Canonical Parsing - infiniTY", - "canonical_bson": "180000001364000000000000000000000000000000007800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"infiniTY\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" - }, - { - "description": "Non-Canonical Parsing - inf", - "canonical_bson": "180000001364000000000000000000000000000000007800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"inf\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" - }, - { - "description": "Non-Canonical Parsing - inF", - "canonical_bson": "180000001364000000000000000000000000000000007800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"inF\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" - }, - { - "description": "Non-Canonical Parsing - -infinity", - "canonical_bson": "18000000136400000000000000000000000000000000F800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-infinity\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" - }, - { - "description": "Non-Canonical Parsing - -infiniTy", - "canonical_bson": "18000000136400000000000000000000000000000000F800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-infiniTy\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" - }, - { - "description": "Non-Canonical Parsing - -Inf", - "canonical_bson": "18000000136400000000000000000000000000000000F800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" - }, - { - "description": "Non-Canonical Parsing - -inf", - "canonical_bson": "18000000136400000000000000000000000000000000F800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-inf\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" - }, - { - "description": "Non-Canonical Parsing - -inF", - "canonical_bson": "18000000136400000000000000000000000000000000F800", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-inF\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" - }, - { - "description": "Rounded Subnormal number", - "canonical_bson": "180000001364000100000000000000000000000000000000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10E-6177\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" - }, - { - "description": "Clamped", - "canonical_bson": "180000001364000a00000000000000000000000000fe5f00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E6112\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+6112\"}}" - }, - { - "description": "Exact rounding", - "canonical_bson": "18000000136400000000000a5bc138938d44c64d31cc3700", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+999\"}}" - }, - { - "description": "Clamped zeros with a large positive exponent", - "canonical_bson": "180000001364000000000000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+2147483647\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6111\"}}" - }, - { - "description": "Clamped zeros with a large negative exponent", - "canonical_bson": "180000001364000000000000000000000000000000000000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-2147483647\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6176\"}}" - }, - { - "description": "Clamped negative zeros with a large positive exponent", - "canonical_bson": "180000001364000000000000000000000000000000FEDF00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+2147483647\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6111\"}}" - }, - { - "description": "Clamped negative zeros with a large negative exponent", - "canonical_bson": "180000001364000000000000000000000000000000008000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-2147483647\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6176\"}}" - } - ] -} diff --git a/bson/src/test/resources/bson/decimal128-2.json b/bson/src/test/resources/bson/decimal128-2.json deleted file mode 100644 index 316d3b0e618..00000000000 --- a/bson/src/test/resources/bson/decimal128-2.json +++ /dev/null @@ -1,793 +0,0 @@ -{ - "description": "Decimal128", - "bson_type": "0x13", - "test_key": "d", - "valid": [ - { - "description": "[decq021] Normality", - "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3C40B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1234567890123456789012345678901234\"}}" - }, - { - "description": "[decq823] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "18000000136400010000800000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-2147483649\"}}" - }, - { - "description": "[decq822] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "18000000136400000000800000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-2147483648\"}}" - }, - { - "description": "[decq821] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "18000000136400FFFFFF7F0000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-2147483647\"}}" - }, - { - "description": "[decq820] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "18000000136400FEFFFF7F0000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-2147483646\"}}" - }, - { - "description": "[decq152] fold-downs (more below)", - "canonical_bson": "18000000136400393000000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-12345\"}}" - }, - { - "description": "[decq154] fold-downs (more below)", - "canonical_bson": "18000000136400D20400000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1234\"}}" - }, - { - "description": "[decq006] derivative canonical plain strings", - "canonical_bson": "18000000136400EE0200000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-750\"}}" - }, - { - "description": "[decq164] fold-downs (more below)", - "canonical_bson": "1800000013640039300000000000000000000000003CB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-123.45\"}}" - }, - { - "description": "[decq156] fold-downs (more below)", - "canonical_bson": "180000001364007B0000000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-123\"}}" - }, - { - "description": "[decq008] derivative canonical plain strings", - "canonical_bson": "18000000136400EE020000000000000000000000003EB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-75.0\"}}" - }, - { - "description": "[decq158] fold-downs (more below)", - "canonical_bson": "180000001364000C0000000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-12\"}}" - }, - { - "description": "[decq122] Nmax and similar", - "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09EDFFDF00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.999999999999999999999999999999999E+6144\"}}" - }, - { - "description": "[decq002] (mostly derived from the Strawman 4 document and examples)", - "canonical_bson": "18000000136400EE020000000000000000000000003CB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-7.50\"}}" - }, - { - "description": "[decq004] derivative canonical plain strings", - "canonical_bson": "18000000136400EE0200000000000000000000000042B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-7.50E+3\"}}" - }, - { - "description": "[decq018] derivative canonical plain strings", - "canonical_bson": "18000000136400EE020000000000000000000000002EB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-7.50E-7\"}}" - }, - { - "description": "[decq125] Nmax and similar", - "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CFEDF00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.234567890123456789012345678901234E+6144\"}}" - }, - { - "description": "[decq131] fold-downs (more below)", - "canonical_bson": "18000000136400000000807F1BCF85B27059C8A43CFEDF00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.230000000000000000000000000000000E+6144\"}}" - }, - { - "description": "[decq162] fold-downs (more below)", - "canonical_bson": "180000001364007B000000000000000000000000003CB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.23\"}}" - }, - { - "description": "[decq176] Nmin and below", - "canonical_bson": "18000000136400010000000A5BC138938D44C64D31008000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.000000000000000000000000000000001E-6143\"}}" - }, - { - "description": "[decq174] Nmin and below", - "canonical_bson": "18000000136400000000000A5BC138938D44C64D31008000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.000000000000000000000000000000000E-6143\"}}" - }, - { - "description": "[decq133] fold-downs (more below)", - "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FEDF00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.000000000000000000000000000000000E+6144\"}}" - }, - { - "description": "[decq160] fold-downs (more below)", - "canonical_bson": "18000000136400010000000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1\"}}" - }, - { - "description": "[decq172] Nmin and below", - "canonical_bson": "180000001364000100000000000000000000000000428000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6143\"}}" - }, - { - "description": "[decq010] derivative canonical plain strings", - "canonical_bson": "18000000136400EE020000000000000000000000003AB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.750\"}}" - }, - { - "description": "[decq012] derivative canonical plain strings", - "canonical_bson": "18000000136400EE0200000000000000000000000038B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0750\"}}" - }, - { - "description": "[decq014] derivative canonical plain strings", - "canonical_bson": "18000000136400EE0200000000000000000000000034B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000750\"}}" - }, - { - "description": "[decq016] derivative canonical plain strings", - "canonical_bson": "18000000136400EE0200000000000000000000000030B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000750\"}}" - }, - { - "description": "[decq404] zeros", - "canonical_bson": "180000001364000000000000000000000000000000000000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6176\"}}" - }, - { - "description": "[decq424] negative zeros", - "canonical_bson": "180000001364000000000000000000000000000000008000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6176\"}}" - }, - { - "description": "[decq407] zeros", - "canonical_bson": "1800000013640000000000000000000000000000003C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" - }, - { - "description": "[decq427] negative zeros", - "canonical_bson": "1800000013640000000000000000000000000000003CB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" - }, - { - "description": "[decq409] zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[decq428] negative zeros", - "canonical_bson": "18000000136400000000000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" - }, - { - "description": "[decq700] Selected DPD codes", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[decq406] zeros", - "canonical_bson": "1800000013640000000000000000000000000000003C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" - }, - { - "description": "[decq426] negative zeros", - "canonical_bson": "1800000013640000000000000000000000000000003CB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" - }, - { - "description": "[decq410] zeros", - "canonical_bson": "180000001364000000000000000000000000000000463000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}" - }, - { - "description": "[decq431] negative zeros", - "canonical_bson": "18000000136400000000000000000000000000000046B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+3\"}}" - }, - { - "description": "[decq419] clamped zeros...", - "canonical_bson": "180000001364000000000000000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6111\"}}" - }, - { - "description": "[decq432] negative zeros", - "canonical_bson": "180000001364000000000000000000000000000000FEDF00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6111\"}}" - }, - { - "description": "[decq405] zeros", - "canonical_bson": "180000001364000000000000000000000000000000000000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6176\"}}" - }, - { - "description": "[decq425] negative zeros", - "canonical_bson": "180000001364000000000000000000000000000000008000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6176\"}}" - }, - { - "description": "[decq508] Specials", - "canonical_bson": "180000001364000000000000000000000000000000007800", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" - }, - { - "description": "[decq528] Specials", - "canonical_bson": "18000000136400000000000000000000000000000000F800", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" - }, - { - "description": "[decq541] Specials", - "canonical_bson": "180000001364000000000000000000000000000000007C00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}" - }, - { - "description": "[decq074] Nmin and below", - "canonical_bson": "18000000136400000000000A5BC138938D44C64D31000000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E-6143\"}}" - }, - { - "description": "[decq602] fold-down full sequence", - "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+6144\"}}" - }, - { - "description": "[decq604] fold-down full sequence", - "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000000E+6143\"}}" - }, - { - "description": "[decq606] fold-down full sequence", - "canonical_bson": "1800000013640000000080264B91C02220BE377E00FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000000000E+6142\"}}" - }, - { - "description": "[decq608] fold-down full sequence", - "canonical_bson": "1800000013640000000040EAED7446D09C2C9F0C00FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000E+6141\"}}" - }, - { - "description": "[decq610] fold-down full sequence", - "canonical_bson": "18000000136400000000A0CA17726DAE0F1E430100FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000E+6140\"}}" - }, - { - "description": "[decq612] fold-down full sequence", - "canonical_bson": "18000000136400000000106102253E5ECE4F200000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000000E+6139\"}}" - }, - { - "description": "[decq614] fold-down full sequence", - "canonical_bson": "18000000136400000000E83C80D09F3C2E3B030000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000E+6138\"}}" - }, - { - "description": "[decq616] fold-down full sequence", - "canonical_bson": "18000000136400000000E4D20CC8DCD2B752000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000E+6137\"}}" - }, - { - "description": "[decq618] fold-down full sequence", - "canonical_bson": "180000001364000000004A48011416954508000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000E+6136\"}}" - }, - { - "description": "[decq620] fold-down full sequence", - "canonical_bson": "18000000136400000000A1EDCCCE1BC2D300000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000E+6135\"}}" - }, - { - "description": "[decq622] fold-down full sequence", - "canonical_bson": "18000000136400000080F64AE1C7022D1500000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000E+6134\"}}" - }, - { - "description": "[decq624] fold-down full sequence", - "canonical_bson": "18000000136400000040B2BAC9E0191E0200000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000E+6133\"}}" - }, - { - "description": "[decq626] fold-down full sequence", - "canonical_bson": "180000001364000000A0DEC5ADC935360000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000E+6132\"}}" - }, - { - "description": "[decq628] fold-down full sequence", - "canonical_bson": "18000000136400000010632D5EC76B050000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000E+6131\"}}" - }, - { - "description": "[decq630] fold-down full sequence", - "canonical_bson": "180000001364000000E8890423C78A000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000E+6130\"}}" - }, - { - "description": "[decq632] fold-down full sequence", - "canonical_bson": "18000000136400000064A7B3B6E00D000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000E+6129\"}}" - }, - { - "description": "[decq634] fold-down full sequence", - "canonical_bson": "1800000013640000008A5D78456301000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000E+6128\"}}" - }, - { - "description": "[decq636] fold-down full sequence", - "canonical_bson": "180000001364000000C16FF2862300000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000E+6127\"}}" - }, - { - "description": "[decq638] fold-down full sequence", - "canonical_bson": "180000001364000080C6A47E8D0300000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000E+6126\"}}" - }, - { - "description": "[decq640] fold-down full sequence", - "canonical_bson": "1800000013640000407A10F35A0000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000E+6125\"}}" - }, - { - "description": "[decq642] fold-down full sequence", - "canonical_bson": "1800000013640000A0724E18090000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000E+6124\"}}" - }, - { - "description": "[decq644] fold-down full sequence", - "canonical_bson": "180000001364000010A5D4E8000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000E+6123\"}}" - }, - { - "description": "[decq646] fold-down full sequence", - "canonical_bson": "1800000013640000E8764817000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000E+6122\"}}" - }, - { - "description": "[decq648] fold-down full sequence", - "canonical_bson": "1800000013640000E40B5402000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000E+6121\"}}" - }, - { - "description": "[decq650] fold-down full sequence", - "canonical_bson": "1800000013640000CA9A3B00000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000E+6120\"}}" - }, - { - "description": "[decq652] fold-down full sequence", - "canonical_bson": "1800000013640000E1F50500000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000E+6119\"}}" - }, - { - "description": "[decq654] fold-down full sequence", - "canonical_bson": "180000001364008096980000000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000E+6118\"}}" - }, - { - "description": "[decq656] fold-down full sequence", - "canonical_bson": "1800000013640040420F0000000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000E+6117\"}}" - }, - { - "description": "[decq658] fold-down full sequence", - "canonical_bson": "18000000136400A086010000000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000E+6116\"}}" - }, - { - "description": "[decq660] fold-down full sequence", - "canonical_bson": "180000001364001027000000000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000E+6115\"}}" - }, - { - "description": "[decq662] fold-down full sequence", - "canonical_bson": "18000000136400E803000000000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000E+6114\"}}" - }, - { - "description": "[decq664] fold-down full sequence", - "canonical_bson": "180000001364006400000000000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+6113\"}}" - }, - { - "description": "[decq666] fold-down full sequence", - "canonical_bson": "180000001364000A00000000000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+6112\"}}" - }, - { - "description": "[decq060] fold-downs (more below)", - "canonical_bson": "180000001364000100000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1\"}}" - }, - { - "description": "[decq670] fold-down full sequence", - "canonical_bson": "180000001364000100000000000000000000000000FC5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6110\"}}" - }, - { - "description": "[decq668] fold-down full sequence", - "canonical_bson": "180000001364000100000000000000000000000000FE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6111\"}}" - }, - { - "description": "[decq072] Nmin and below", - "canonical_bson": "180000001364000100000000000000000000000000420000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6143\"}}" - }, - { - "description": "[decq076] Nmin and below", - "canonical_bson": "18000000136400010000000A5BC138938D44C64D31000000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000001E-6143\"}}" - }, - { - "description": "[decq036] fold-downs (more below)", - "canonical_bson": "18000000136400000000807F1BCF85B27059C8A43CFE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.230000000000000000000000000000000E+6144\"}}" - }, - { - "description": "[decq062] fold-downs (more below)", - "canonical_bson": "180000001364007B000000000000000000000000003C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.23\"}}" - }, - { - "description": "[decq034] Nmax and similar", - "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CFE5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.234567890123456789012345678901234E+6144\"}}" - }, - { - "description": "[decq441] exponent lengths", - "canonical_bson": "180000001364000700000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7\"}}" - }, - { - "description": "[decq449] exponent lengths", - "canonical_bson": "1800000013640007000000000000000000000000001E5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+5999\"}}" - }, - { - "description": "[decq447] exponent lengths", - "canonical_bson": "1800000013640007000000000000000000000000000E3800", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+999\"}}" - }, - { - "description": "[decq445] exponent lengths", - "canonical_bson": "180000001364000700000000000000000000000000063100", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+99\"}}" - }, - { - "description": "[decq443] exponent lengths", - "canonical_bson": "180000001364000700000000000000000000000000523000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+9\"}}" - }, - { - "description": "[decq842] VG testcase", - "canonical_bson": "180000001364000000FED83F4E7C9FE4E269E38A5BCD1700", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7.049000000000010795488000000000000E-3097\"}}" - }, - { - "description": "[decq841] VG testcase", - "canonical_bson": "180000001364000000203B9DB5056F000000000000002400", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"8.000000000000000000E-1550\"}}" - }, - { - "description": "[decq840] VG testcase", - "canonical_bson": "180000001364003C17258419D710C42F0000000000002400", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"8.81125000000001349436E-1548\"}}" - }, - { - "description": "[decq701] Selected DPD codes", - "canonical_bson": "180000001364000900000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9\"}}" - }, - { - "description": "[decq032] Nmax and similar", - "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09EDFF5F00", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9.999999999999999999999999999999999E+6144\"}}" - }, - { - "description": "[decq702] Selected DPD codes", - "canonical_bson": "180000001364000A00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10\"}}" - }, - { - "description": "[decq057] fold-downs (more below)", - "canonical_bson": "180000001364000C00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12\"}}" - }, - { - "description": "[decq703] Selected DPD codes", - "canonical_bson": "180000001364001300000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"19\"}}" - }, - { - "description": "[decq704] Selected DPD codes", - "canonical_bson": "180000001364001400000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"20\"}}" - }, - { - "description": "[decq705] Selected DPD codes", - "canonical_bson": "180000001364001D00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"29\"}}" - }, - { - "description": "[decq706] Selected DPD codes", - "canonical_bson": "180000001364001E00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"30\"}}" - }, - { - "description": "[decq707] Selected DPD codes", - "canonical_bson": "180000001364002700000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"39\"}}" - }, - { - "description": "[decq708] Selected DPD codes", - "canonical_bson": "180000001364002800000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"40\"}}" - }, - { - "description": "[decq709] Selected DPD codes", - "canonical_bson": "180000001364003100000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"49\"}}" - }, - { - "description": "[decq710] Selected DPD codes", - "canonical_bson": "180000001364003200000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"50\"}}" - }, - { - "description": "[decq711] Selected DPD codes", - "canonical_bson": "180000001364003B00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"59\"}}" - }, - { - "description": "[decq712] Selected DPD codes", - "canonical_bson": "180000001364003C00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"60\"}}" - }, - { - "description": "[decq713] Selected DPD codes", - "canonical_bson": "180000001364004500000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"69\"}}" - }, - { - "description": "[decq714] Selected DPD codes", - "canonical_bson": "180000001364004600000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"70\"}}" - }, - { - "description": "[decq715] Selected DPD codes", - "canonical_bson": "180000001364004700000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"71\"}}" - }, - { - "description": "[decq716] Selected DPD codes", - "canonical_bson": "180000001364004800000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"72\"}}" - }, - { - "description": "[decq717] Selected DPD codes", - "canonical_bson": "180000001364004900000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"73\"}}" - }, - { - "description": "[decq718] Selected DPD codes", - "canonical_bson": "180000001364004A00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"74\"}}" - }, - { - "description": "[decq719] Selected DPD codes", - "canonical_bson": "180000001364004B00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"75\"}}" - }, - { - "description": "[decq720] Selected DPD codes", - "canonical_bson": "180000001364004C00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"76\"}}" - }, - { - "description": "[decq721] Selected DPD codes", - "canonical_bson": "180000001364004D00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"77\"}}" - }, - { - "description": "[decq722] Selected DPD codes", - "canonical_bson": "180000001364004E00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"78\"}}" - }, - { - "description": "[decq723] Selected DPD codes", - "canonical_bson": "180000001364004F00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"79\"}}" - }, - { - "description": "[decq056] fold-downs (more below)", - "canonical_bson": "180000001364007B00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123\"}}" - }, - { - "description": "[decq064] fold-downs (more below)", - "canonical_bson": "1800000013640039300000000000000000000000003C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123.45\"}}" - }, - { - "description": "[decq732] Selected DPD codes", - "canonical_bson": "180000001364000802000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"520\"}}" - }, - { - "description": "[decq733] Selected DPD codes", - "canonical_bson": "180000001364000902000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"521\"}}" - }, - { - "description": "[decq740] DPD: one of each of the huffman groups", - "canonical_bson": "180000001364000903000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"777\"}}" - }, - { - "description": "[decq741] DPD: one of each of the huffman groups", - "canonical_bson": "180000001364000A03000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"778\"}}" - }, - { - "description": "[decq742] DPD: one of each of the huffman groups", - "canonical_bson": "180000001364001303000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"787\"}}" - }, - { - "description": "[decq746] DPD: one of each of the huffman groups", - "canonical_bson": "180000001364001F03000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"799\"}}" - }, - { - "description": "[decq743] DPD: one of each of the huffman groups", - "canonical_bson": "180000001364006D03000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"877\"}}" - }, - { - "description": "[decq753] DPD all-highs cases (includes the 24 redundant codes)", - "canonical_bson": "180000001364007803000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"888\"}}" - }, - { - "description": "[decq754] DPD all-highs cases (includes the 24 redundant codes)", - "canonical_bson": "180000001364007903000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"889\"}}" - }, - { - "description": "[decq760] DPD all-highs cases (includes the 24 redundant codes)", - "canonical_bson": "180000001364008203000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"898\"}}" - }, - { - "description": "[decq764] DPD all-highs cases (includes the 24 redundant codes)", - "canonical_bson": "180000001364008303000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"899\"}}" - }, - { - "description": "[decq745] DPD: one of each of the huffman groups", - "canonical_bson": "18000000136400D303000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"979\"}}" - }, - { - "description": "[decq770] DPD all-highs cases (includes the 24 redundant codes)", - "canonical_bson": "18000000136400DC03000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"988\"}}" - }, - { - "description": "[decq774] DPD all-highs cases (includes the 24 redundant codes)", - "canonical_bson": "18000000136400DD03000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"989\"}}" - }, - { - "description": "[decq730] Selected DPD codes", - "canonical_bson": "18000000136400E203000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"994\"}}" - }, - { - "description": "[decq731] Selected DPD codes", - "canonical_bson": "18000000136400E303000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"995\"}}" - }, - { - "description": "[decq744] DPD: one of each of the huffman groups", - "canonical_bson": "18000000136400E503000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"997\"}}" - }, - { - "description": "[decq780] DPD all-highs cases (includes the 24 redundant codes)", - "canonical_bson": "18000000136400E603000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"998\"}}" - }, - { - "description": "[decq787] DPD all-highs cases (includes the 24 redundant codes)", - "canonical_bson": "18000000136400E703000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"999\"}}" - }, - { - "description": "[decq053] fold-downs (more below)", - "canonical_bson": "18000000136400D204000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1234\"}}" - }, - { - "description": "[decq052] fold-downs (more below)", - "canonical_bson": "180000001364003930000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12345\"}}" - }, - { - "description": "[decq792] Miscellaneous (testers' queries, etc.)", - "canonical_bson": "180000001364003075000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"30000\"}}" - }, - { - "description": "[decq793] Miscellaneous (testers' queries, etc.)", - "canonical_bson": "1800000013640090940D0000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"890000\"}}" - }, - { - "description": "[decq824] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "18000000136400FEFFFF7F00000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2147483646\"}}" - }, - { - "description": "[decq825] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "18000000136400FFFFFF7F00000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2147483647\"}}" - }, - { - "description": "[decq826] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "180000001364000000008000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2147483648\"}}" - }, - { - "description": "[decq827] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "180000001364000100008000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2147483649\"}}" - }, - { - "description": "[decq828] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "18000000136400FEFFFFFF00000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4294967294\"}}" - }, - { - "description": "[decq829] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "18000000136400FFFFFFFF00000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4294967295\"}}" - }, - { - "description": "[decq830] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "180000001364000000000001000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4294967296\"}}" - }, - { - "description": "[decq831] values around [u]int32 edges (zeros done earlier)", - "canonical_bson": "180000001364000100000001000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4294967297\"}}" - }, - { - "description": "[decq022] Normality", - "canonical_bson": "18000000136400C7711CC7B548F377DC80A131C836403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1111111111111111111111111111111111\"}}" - }, - { - "description": "[decq020] Normality", - "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3C403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1234567890123456789012345678901234\"}}" - }, - { - "description": "[decq550] Specials", - "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09ED413000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9999999999999999999999999999999999\"}}" - } - ] -} - diff --git a/bson/src/test/resources/bson/decimal128-3.json b/bson/src/test/resources/bson/decimal128-3.json deleted file mode 100644 index 9b015343ce7..00000000000 --- a/bson/src/test/resources/bson/decimal128-3.json +++ /dev/null @@ -1,1771 +0,0 @@ -{ - "description": "Decimal128", - "bson_type": "0x13", - "test_key": "d", - "valid": [ - { - "description": "[basx066] strings without E cannot generate E in result", - "canonical_bson": "18000000136400185C0ACE0000000000000000000038B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-00345678.5432\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-345678.5432\"}}" - }, - { - "description": "[basx065] strings without E cannot generate E in result", - "canonical_bson": "18000000136400185C0ACE0000000000000000000038B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0345678.5432\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-345678.5432\"}}" - }, - { - "description": "[basx064] strings without E cannot generate E in result", - "canonical_bson": "18000000136400185C0ACE0000000000000000000038B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-345678.5432\"}}" - }, - { - "description": "[basx041] strings without E cannot generate E in result", - "canonical_bson": "180000001364004C0000000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-76\"}}" - }, - { - "description": "[basx027] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364000F270000000000000000000000003AB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.999\"}}" - }, - { - "description": "[basx026] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364009F230000000000000000000000003AB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.119\"}}" - }, - { - "description": "[basx025] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364008F030000000000000000000000003CB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.11\"}}" - }, - { - "description": "[basx024] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364005B000000000000000000000000003EB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.1\"}}" - }, - { - "description": "[dqbsr531] negatives (Rounded)", - "canonical_bson": "1800000013640099761CC7B548F377DC80A131C836FEAF00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.1111111111111111111111111111123450\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.111111111111111111111111111112345\"}}" - }, - { - "description": "[basx022] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364000A000000000000000000000000003EB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.0\"}}" - }, - { - "description": "[basx021] conform to rules and exponent will be in permitted range).", - "canonical_bson": "18000000136400010000000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1\"}}" - }, - { - "description": "[basx601] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000002E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-9\"}}" - }, - { - "description": "[basx622] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000002EB000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-9\"}}" - }, - { - "description": "[basx602] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000303000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-8\"}}" - }, - { - "description": "[basx621] Zeros", - "canonical_bson": "18000000136400000000000000000000000000000030B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-8\"}}" - }, - { - "description": "[basx603] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" - }, - { - "description": "[basx620] Zeros", - "canonical_bson": "18000000136400000000000000000000000000000032B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-7\"}}" - }, - { - "description": "[basx604] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000343000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" - }, - { - "description": "[basx619] Zeros", - "canonical_bson": "18000000136400000000000000000000000000000034B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000\"}}" - }, - { - "description": "[basx605] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000363000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" - }, - { - "description": "[basx618] Zeros", - "canonical_bson": "18000000136400000000000000000000000000000036B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000\"}}" - }, - { - "description": "[basx680] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"000000.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx606] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000383000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" - }, - { - "description": "[basx617] Zeros", - "canonical_bson": "18000000136400000000000000000000000000000038B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000\"}}" - }, - { - "description": "[basx681] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00000.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx686] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+00000.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx687] Zeros", - "canonical_bson": "18000000136400000000000000000000000000000040B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-00000.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" - }, - { - "description": "[basx019] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640000000000000000000000000000003CB000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-00.00\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" - }, - { - "description": "[basx607] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000\"}}" - }, - { - "description": "[basx616] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003AB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000\"}}" - }, - { - "description": "[basx682] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0000.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx155] Numbers with E", - "canonical_bson": "1800000013640000000000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000e+0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000\"}}" - }, - { - "description": "[basx130] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" - }, - { - "description": "[basx290] some more negative zeros [systematic tests below]", - "canonical_bson": "18000000136400000000000000000000000000000038B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000\"}}" - }, - { - "description": "[basx131] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" - }, - { - "description": "[basx291] some more negative zeros [systematic tests below]", - "canonical_bson": "18000000136400000000000000000000000000000036B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000\"}}" - }, - { - "description": "[basx132] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" - }, - { - "description": "[basx292] some more negative zeros [systematic tests below]", - "canonical_bson": "18000000136400000000000000000000000000000034B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000\"}}" - }, - { - "description": "[basx133] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" - }, - { - "description": "[basx293] some more negative zeros [systematic tests below]", - "canonical_bson": "18000000136400000000000000000000000000000032B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-7\"}}" - }, - { - "description": "[basx608] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" - }, - { - "description": "[basx615] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003CB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" - }, - { - "description": "[basx683] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"000.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx630] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" - }, - { - "description": "[basx670] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" - }, - { - "description": "[basx631] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" - }, - { - "description": "[basx671] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000\"}}" - }, - { - "description": "[basx134] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" - }, - { - "description": "[basx294] some more negative zeros [systematic tests below]", - "canonical_bson": "18000000136400000000000000000000000000000038B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000\"}}" - }, - { - "description": "[basx632] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx672] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" - }, - { - "description": "[basx135] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" - }, - { - "description": "[basx295] some more negative zeros [systematic tests below]", - "canonical_bson": "18000000136400000000000000000000000000000036B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000\"}}" - }, - { - "description": "[basx633] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000423000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+1\"}}" - }, - { - "description": "[basx673] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" - }, - { - "description": "[basx136] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" - }, - { - "description": "[basx674] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" - }, - { - "description": "[basx634] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000443000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+2\"}}" - }, - { - "description": "[basx137] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" - }, - { - "description": "[basx635] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000463000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}" - }, - { - "description": "[basx675] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" - }, - { - "description": "[basx636] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000483000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+6\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+4\"}}" - }, - { - "description": "[basx676] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000303000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-6\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-8\"}}" - }, - { - "description": "[basx637] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000004A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+7\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+5\"}}" - }, - { - "description": "[basx677] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000002E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-7\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-9\"}}" - }, - { - "description": "[basx638] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000004C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6\"}}" - }, - { - "description": "[basx678] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000002C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-10\"}}" - }, - { - "description": "[basx149] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"000E+9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" - }, - { - "description": "[basx639] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000004E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+7\"}}" - }, - { - "description": "[basx679] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000002A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-11\"}}" - }, - { - "description": "[basx063] strings without E cannot generate E in result", - "canonical_bson": "18000000136400185C0ACE00000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+00345678.5432\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.5432\"}}" - }, - { - "description": "[basx018] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640000000000000000000000000000003EB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0\"}}" - }, - { - "description": "[basx609] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" - }, - { - "description": "[basx614] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003EB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0\"}}" - }, - { - "description": "[basx684] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx640] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" - }, - { - "description": "[basx660] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" - }, - { - "description": "[basx641] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx661] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" - }, - { - "description": "[basx296] some more negative zeros [systematic tests below]", - "canonical_bson": "1800000013640000000000000000000000000000003AB000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000\"}}" - }, - { - "description": "[basx642] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000423000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+1\"}}" - }, - { - "description": "[basx662] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000\"}}" - }, - { - "description": "[basx297] some more negative zeros [systematic tests below]", - "canonical_bson": "18000000136400000000000000000000000000000038B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000\"}}" - }, - { - "description": "[basx643] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000443000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+2\"}}" - }, - { - "description": "[basx663] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" - }, - { - "description": "[basx644] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000463000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}" - }, - { - "description": "[basx664] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" - }, - { - "description": "[basx645] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000483000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+4\"}}" - }, - { - "description": "[basx665] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" - }, - { - "description": "[basx646] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000004A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+6\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+5\"}}" - }, - { - "description": "[basx666] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-6\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" - }, - { - "description": "[basx647] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000004C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+7\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6\"}}" - }, - { - "description": "[basx667] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000303000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-7\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-8\"}}" - }, - { - "description": "[basx648] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000004E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+7\"}}" - }, - { - "description": "[basx668] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000002E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-9\"}}" - }, - { - "description": "[basx160] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00E+9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" - }, - { - "description": "[basx161] Numbers with E", - "canonical_bson": "1800000013640000000000000000000000000000002E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00E-9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-9\"}}" - }, - { - "description": "[basx649] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000503000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+8\"}}" - }, - { - "description": "[basx669] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000002C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-10\"}}" - }, - { - "description": "[basx062] strings without E cannot generate E in result", - "canonical_bson": "18000000136400185C0ACE00000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+0345678.5432\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.5432\"}}" - }, - { - "description": "[basx001] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx017] conform to rules and exponent will be in permitted range).", - "canonical_bson": "18000000136400000000000000000000000000000040B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" - }, - { - "description": "[basx611] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx613] Zeros", - "canonical_bson": "18000000136400000000000000000000000000000040B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" - }, - { - "description": "[basx685] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx688] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+0.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx689] Zeros", - "canonical_bson": "18000000136400000000000000000000000000000040B000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" - }, - { - "description": "[basx650] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" - }, - { - "description": "[basx651] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000423000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+1\"}}" - }, - { - "description": "[basx298] some more negative zeros [systematic tests below]", - "canonical_bson": "1800000013640000000000000000000000000000003CB000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" - }, - { - "description": "[basx652] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000443000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+2\"}}" - }, - { - "description": "[basx299] some more negative zeros [systematic tests below]", - "canonical_bson": "1800000013640000000000000000000000000000003AB000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000\"}}" - }, - { - "description": "[basx653] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000463000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}" - }, - { - "description": "[basx654] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000483000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+4\"}}" - }, - { - "description": "[basx655] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000004A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+5\"}}" - }, - { - "description": "[basx656] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000004C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6\"}}" - }, - { - "description": "[basx657] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000004E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+7\"}}" - }, - { - "description": "[basx658] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000503000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+8\"}}" - }, - { - "description": "[basx138] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+0E+9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" - }, - { - "description": "[basx139] Numbers with E", - "canonical_bson": "18000000136400000000000000000000000000000052B000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+9\"}}" - }, - { - "description": "[basx144] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000523000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" - }, - { - "description": "[basx154] Numbers with E", - "canonical_bson": "180000001364000000000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" - }, - { - "description": "[basx659] Zeros", - "canonical_bson": "180000001364000000000000000000000000000000523000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" - }, - { - "description": "[basx042] strings without E cannot generate E in result", - "canonical_bson": "18000000136400FC040000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+12.76\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.76\"}}" - }, - { - "description": "[basx143] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+1E+009\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" - }, - { - "description": "[basx061] strings without E cannot generate E in result", - "canonical_bson": "18000000136400185C0ACE00000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+345678.5432\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.5432\"}}" - }, - { - "description": "[basx036] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640015CD5B0700000000000000000000203000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000000123456789\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.23456789E-8\"}}" - }, - { - "description": "[basx035] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640015CD5B0700000000000000000000223000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000123456789\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.23456789E-7\"}}" - }, - { - "description": "[basx034] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640015CD5B0700000000000000000000243000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000123456789\"}}" - }, - { - "description": "[basx053] strings without E cannot generate E in result", - "canonical_bson": "180000001364003200000000000000000000000000323000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000050\"}}" - }, - { - "description": "[basx033] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640015CD5B0700000000000000000000263000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000123456789\"}}" - }, - { - "description": "[basx016] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364000C000000000000000000000000003A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.012\"}}" - }, - { - "description": "[basx015] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364007B000000000000000000000000003A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.123\"}}" - }, - { - "description": "[basx037] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640078DF0D8648700000000000000000223000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.123456789012344\"}}" - }, - { - "description": "[basx038] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640079DF0D8648700000000000000000223000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.123456789012345\"}}" - }, - { - "description": "[basx250] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000383000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" - }, - { - "description": "[basx257] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" - }, - { - "description": "[basx256] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.01265\"}}" - }, - { - "description": "[basx258] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" - }, - { - "description": "[basx251] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000103000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-20\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-21\"}}" - }, - { - "description": "[basx263] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000603000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+20\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+19\"}}" - }, - { - "description": "[basx255] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.001265\"}}" - }, - { - "description": "[basx259] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" - }, - { - "description": "[basx254] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0001265\"}}" - }, - { - "description": "[basx260] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" - }, - { - "description": "[basx253] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000303000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00001265\"}}" - }, - { - "description": "[basx261] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" - }, - { - "description": "[basx252] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000283000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-9\"}}" - }, - { - "description": "[basx262] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000483000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+7\"}}" - }, - { - "description": "[basx159] Numbers with E", - "canonical_bson": "1800000013640049000000000000000000000000002E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.73e-7\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7.3E-8\"}}" - }, - { - "description": "[basx004] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640064000000000000000000000000003C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00\"}}" - }, - { - "description": "[basx003] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364000A000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0\"}}" - }, - { - "description": "[basx002] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364000100000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1\"}}" - }, - { - "description": "[basx148] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+009\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" - }, - { - "description": "[basx153] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E009\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" - }, - { - "description": "[basx141] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e+09\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" - }, - { - "description": "[basx146] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+09\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" - }, - { - "description": "[basx151] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e09\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" - }, - { - "description": "[basx142] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000F43000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+90\"}}" - }, - { - "description": "[basx147] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000F43000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e+90\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+90\"}}" - }, - { - "description": "[basx152] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000F43000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E90\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+90\"}}" - }, - { - "description": "[basx140] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000523000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" - }, - { - "description": "[basx150] Numbers with E", - "canonical_bson": "180000001364000100000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" - }, - { - "description": "[basx014] conform to rules and exponent will be in permitted range).", - "canonical_bson": "18000000136400D2040000000000000000000000003A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.234\"}}" - }, - { - "description": "[basx170] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" - }, - { - "description": "[basx177] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" - }, - { - "description": "[basx176] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" - }, - { - "description": "[basx178] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" - }, - { - "description": "[basx171] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000123000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-20\"}}" - }, - { - "description": "[basx183] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000623000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+20\"}}" - }, - { - "description": "[basx175] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.01265\"}}" - }, - { - "description": "[basx179] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" - }, - { - "description": "[basx174] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.001265\"}}" - }, - { - "description": "[basx180] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" - }, - { - "description": "[basx173] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0001265\"}}" - }, - { - "description": "[basx181] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000423000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+4\"}}" - }, - { - "description": "[basx172] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000002A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-8\"}}" - }, - { - "description": "[basx182] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000004A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+8\"}}" - }, - { - "description": "[basx157] Numbers with E", - "canonical_bson": "180000001364000400000000000000000000000000523000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4E+9\"}}" - }, - { - "description": "[basx067] examples", - "canonical_bson": "180000001364000500000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"5E-6\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000005\"}}" - }, - { - "description": "[basx069] examples", - "canonical_bson": "180000001364000500000000000000000000000000323000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"5E-7\"}}" - }, - { - "description": "[basx385] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7\"}}" - }, - { - "description": "[basx365] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000543000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E10\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+10\"}}" - }, - { - "description": "[basx405] Engineering notation tests", - "canonical_bson": "1800000013640007000000000000000000000000002C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-10\"}}" - }, - { - "description": "[basx363] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000563000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E11\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+11\"}}" - }, - { - "description": "[basx407] Engineering notation tests", - "canonical_bson": "1800000013640007000000000000000000000000002A3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-11\"}}" - }, - { - "description": "[basx361] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000583000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E12\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+12\"}}" - }, - { - "description": "[basx409] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000283000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-12\"}}" - }, - { - "description": "[basx411] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000263000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-13\"}}" - }, - { - "description": "[basx383] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000423000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+1\"}}" - }, - { - "description": "[basx387] Engineering notation tests", - "canonical_bson": "1800000013640007000000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.7\"}}" - }, - { - "description": "[basx381] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000443000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+2\"}}" - }, - { - "description": "[basx389] Engineering notation tests", - "canonical_bson": "1800000013640007000000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.07\"}}" - }, - { - "description": "[basx379] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000463000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+3\"}}" - }, - { - "description": "[basx391] Engineering notation tests", - "canonical_bson": "1800000013640007000000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.007\"}}" - }, - { - "description": "[basx377] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000483000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+4\"}}" - }, - { - "description": "[basx393] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0007\"}}" - }, - { - "description": "[basx375] Engineering notation tests", - "canonical_bson": "1800000013640007000000000000000000000000004A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+5\"}}" - }, - { - "description": "[basx395] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00007\"}}" - }, - { - "description": "[basx373] Engineering notation tests", - "canonical_bson": "1800000013640007000000000000000000000000004C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E6\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+6\"}}" - }, - { - "description": "[basx397] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-6\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000007\"}}" - }, - { - "description": "[basx371] Engineering notation tests", - "canonical_bson": "1800000013640007000000000000000000000000004E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E7\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+7\"}}" - }, - { - "description": "[basx399] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000323000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-7\"}}" - }, - { - "description": "[basx369] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000503000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+8\"}}" - }, - { - "description": "[basx401] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000303000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-8\"}}" - }, - { - "description": "[basx367] Engineering notation tests", - "canonical_bson": "180000001364000700000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+9\"}}" - }, - { - "description": "[basx403] Engineering notation tests", - "canonical_bson": "1800000013640007000000000000000000000000002E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-9\"}}" - }, - { - "description": "[basx007] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640064000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.0\"}}" - }, - { - "description": "[basx005] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364000A00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10\"}}" - }, - { - "description": "[basx165] Numbers with E", - "canonical_bson": "180000001364000A00000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10E+009\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+10\"}}" - }, - { - "description": "[basx163] Numbers with E", - "canonical_bson": "180000001364000A00000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10E+09\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+10\"}}" - }, - { - "description": "[basx325] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10\"}}" - }, - { - "description": "[basx305] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000543000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e10\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+11\"}}" - }, - { - "description": "[basx345] Engineering notation tests", - "canonical_bson": "180000001364000A000000000000000000000000002C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-10\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-9\"}}" - }, - { - "description": "[basx303] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000563000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e11\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+12\"}}" - }, - { - "description": "[basx347] Engineering notation tests", - "canonical_bson": "180000001364000A000000000000000000000000002A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-11\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-10\"}}" - }, - { - "description": "[basx301] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000583000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e12\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+13\"}}" - }, - { - "description": "[basx349] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000283000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-12\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-11\"}}" - }, - { - "description": "[basx351] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000263000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-13\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-12\"}}" - }, - { - "description": "[basx323] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000423000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+2\"}}" - }, - { - "description": "[basx327] Engineering notation tests", - "canonical_bson": "180000001364000A000000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0\"}}" - }, - { - "description": "[basx321] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000443000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+3\"}}" - }, - { - "description": "[basx329] Engineering notation tests", - "canonical_bson": "180000001364000A000000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.10\"}}" - }, - { - "description": "[basx319] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000463000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+4\"}}" - }, - { - "description": "[basx331] Engineering notation tests", - "canonical_bson": "180000001364000A000000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.010\"}}" - }, - { - "description": "[basx317] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000483000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+5\"}}" - }, - { - "description": "[basx333] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0010\"}}" - }, - { - "description": "[basx315] Engineering notation tests", - "canonical_bson": "180000001364000A000000000000000000000000004A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+6\"}}" - }, - { - "description": "[basx335] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00010\"}}" - }, - { - "description": "[basx313] Engineering notation tests", - "canonical_bson": "180000001364000A000000000000000000000000004C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e6\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+7\"}}" - }, - { - "description": "[basx337] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-6\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000010\"}}" - }, - { - "description": "[basx311] Engineering notation tests", - "canonical_bson": "180000001364000A000000000000000000000000004E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e7\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+8\"}}" - }, - { - "description": "[basx339] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-7\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000010\"}}" - }, - { - "description": "[basx309] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000503000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+9\"}}" - }, - { - "description": "[basx341] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000303000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-7\"}}" - }, - { - "description": "[basx164] Numbers with E", - "canonical_bson": "180000001364000A00000000000000000000000000F43000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e+90\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+91\"}}" - }, - { - "description": "[basx162] Numbers with E", - "canonical_bson": "180000001364000A00000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10E+9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+10\"}}" - }, - { - "description": "[basx307] Engineering notation tests", - "canonical_bson": "180000001364000A00000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+10\"}}" - }, - { - "description": "[basx343] Engineering notation tests", - "canonical_bson": "180000001364000A000000000000000000000000002E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-8\"}}" - }, - { - "description": "[basx008] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640065000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.1\"}}" - }, - { - "description": "[basx009] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640068000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.4\"}}" - }, - { - "description": "[basx010] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640069000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.5\"}}" - }, - { - "description": "[basx011] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364006A000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.6\"}}" - }, - { - "description": "[basx012] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364006D000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.9\"}}" - }, - { - "description": "[basx013] conform to rules and exponent will be in permitted range).", - "canonical_bson": "180000001364006E000000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"11.0\"}}" - }, - { - "description": "[basx040] strings without E cannot generate E in result", - "canonical_bson": "180000001364000C00000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12\"}}" - }, - { - "description": "[basx190] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" - }, - { - "description": "[basx197] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" - }, - { - "description": "[basx196] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" - }, - { - "description": "[basx198] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" - }, - { - "description": "[basx191] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000143000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-20\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-19\"}}" - }, - { - "description": "[basx203] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000643000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+20\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+21\"}}" - }, - { - "description": "[basx195] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" - }, - { - "description": "[basx199] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" - }, - { - "description": "[basx194] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.01265\"}}" - }, - { - "description": "[basx200] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000423000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+4\"}}" - }, - { - "description": "[basx193] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000343000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.001265\"}}" - }, - { - "description": "[basx201] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000443000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+5\"}}" - }, - { - "description": "[basx192] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000002C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-7\"}}" - }, - { - "description": "[basx202] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000004C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+9\"}}" - }, - { - "description": "[basx044] strings without E cannot generate E in result", - "canonical_bson": "18000000136400FC040000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"012.76\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.76\"}}" - }, - { - "description": "[basx042] strings without E cannot generate E in result", - "canonical_bson": "18000000136400FC040000000000000000000000003C3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.76\"}}" - }, - { - "description": "[basx046] strings without E cannot generate E in result", - "canonical_bson": "180000001364001100000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"17.\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"17\"}}" - }, - { - "description": "[basx049] strings without E cannot generate E in result", - "canonical_bson": "180000001364002C00000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0044\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"44\"}}" - }, - { - "description": "[basx048] strings without E cannot generate E in result", - "canonical_bson": "180000001364002C00000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"044\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"44\"}}" - }, - { - "description": "[basx158] Numbers with E", - "canonical_bson": "180000001364002C00000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"44E+9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4.4E+10\"}}" - }, - { - "description": "[basx068] examples", - "canonical_bson": "180000001364003200000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"50E-7\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000050\"}}" - }, - { - "description": "[basx169] Numbers with E", - "canonical_bson": "180000001364006400000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"100e+009\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+11\"}}" - }, - { - "description": "[basx167] Numbers with E", - "canonical_bson": "180000001364006400000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"100e+09\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+11\"}}" - }, - { - "description": "[basx168] Numbers with E", - "canonical_bson": "180000001364006400000000000000000000000000F43000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"100E+90\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+92\"}}" - }, - { - "description": "[basx166] Numbers with E", - "canonical_bson": "180000001364006400000000000000000000000000523000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"100e+9\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+11\"}}" - }, - { - "description": "[basx210] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003E3000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" - }, - { - "description": "[basx217] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" - }, - { - "description": "[basx216] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" - }, - { - "description": "[basx218] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" - }, - { - "description": "[basx211] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000163000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-20\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-18\"}}" - }, - { - "description": "[basx223] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000663000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+20\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+22\"}}" - }, - { - "description": "[basx215] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" - }, - { - "description": "[basx219] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000423000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+4\"}}" - }, - { - "description": "[basx214] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" - }, - { - "description": "[basx220] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000443000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+5\"}}" - }, - { - "description": "[basx213] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.01265\"}}" - }, - { - "description": "[basx221] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000463000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+6\"}}" - }, - { - "description": "[basx212] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000002E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000001265\"}}" - }, - { - "description": "[basx222] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000004E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+10\"}}" - }, - { - "description": "[basx006] conform to rules and exponent will be in permitted range).", - "canonical_bson": "18000000136400E803000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1000\"}}" - }, - { - "description": "[basx230] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" - }, - { - "description": "[basx237] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000403000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" - }, - { - "description": "[basx236] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" - }, - { - "description": "[basx238] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000423000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+1\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+4\"}}" - }, - { - "description": "[basx231] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000183000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-20\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-17\"}}" - }, - { - "description": "[basx243] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000683000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+20\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+23\"}}" - }, - { - "description": "[basx235] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" - }, - { - "description": "[basx239] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000443000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+2\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+5\"}}" - }, - { - "description": "[basx234] Numbers with E", - "canonical_bson": "18000000136400F1040000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" - }, - { - "description": "[basx240] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000463000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+3\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+6\"}}" - }, - { - "description": "[basx233] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000383000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" - }, - { - "description": "[basx241] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000483000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+4\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+7\"}}" - }, - { - "description": "[basx232] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000303000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00001265\"}}" - }, - { - "description": "[basx242] Numbers with E", - "canonical_bson": "18000000136400F104000000000000000000000000503000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+8\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+11\"}}" - }, - { - "description": "[basx060] strings without E cannot generate E in result", - "canonical_bson": "18000000136400185C0ACE00000000000000000000383000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.5432\"}}" - }, - { - "description": "[basx059] strings without E cannot generate E in result", - "canonical_bson": "18000000136400F198670C08000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0345678.54321\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.54321\"}}" - }, - { - "description": "[basx058] strings without E cannot generate E in result", - "canonical_bson": "180000001364006AF90B7C50000000000000000000343000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.543210\"}}" - }, - { - "description": "[basx057] strings without E cannot generate E in result", - "canonical_bson": "180000001364006A19562522020000000000000000343000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2345678.543210\"}}" - }, - { - "description": "[basx056] strings without E cannot generate E in result", - "canonical_bson": "180000001364006AB9C8733A0B0000000000000000343000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12345678.543210\"}}" - }, - { - "description": "[basx031] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640040AF0D8648700000000000000000343000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123456789.000000\"}}" - }, - { - "description": "[basx030] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640080910F8648700000000000000000343000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123456789.123456\"}}" - }, - { - "description": "[basx032] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640080910F8648700000000000000000403000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123456789123456\"}}" - } - ] -} diff --git a/bson/src/test/resources/bson/decimal128-4.json b/bson/src/test/resources/bson/decimal128-4.json deleted file mode 100644 index 0957019351f..00000000000 --- a/bson/src/test/resources/bson/decimal128-4.json +++ /dev/null @@ -1,165 +0,0 @@ -{ - "description": "Decimal128", - "bson_type": "0x13", - "test_key": "d", - "valid": [ - { - "description": "[basx023] conform to rules and exponent will be in permitted range).", - "canonical_bson": "1800000013640001000000000000000000000000003EB000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.1\"}}" - }, - - { - "description": "[basx045] strings without E cannot generate E in result", - "canonical_bson": "1800000013640003000000000000000000000000003A3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+0.003\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.003\"}}" - }, - { - "description": "[basx610] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \".0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" - }, - { - "description": "[basx612] Zeros", - "canonical_bson": "1800000013640000000000000000000000000000003EB000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-.0\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0\"}}" - }, - { - "description": "[basx043] strings without E cannot generate E in result", - "canonical_bson": "18000000136400FC040000000000000000000000003C3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+12.76\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.76\"}}" - }, - { - "description": "[basx055] strings without E cannot generate E in result", - "canonical_bson": "180000001364000500000000000000000000000000303000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000005\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"5E-8\"}}" - }, - { - "description": "[basx054] strings without E cannot generate E in result", - "canonical_bson": "180000001364000500000000000000000000000000323000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000005\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"5E-7\"}}" - }, - { - "description": "[basx052] strings without E cannot generate E in result", - "canonical_bson": "180000001364000500000000000000000000000000343000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000005\"}}" - }, - { - "description": "[basx051] strings without E cannot generate E in result", - "canonical_bson": "180000001364000500000000000000000000000000363000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00.00005\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00005\"}}" - }, - { - "description": "[basx050] strings without E cannot generate E in result", - "canonical_bson": "180000001364000500000000000000000000000000383000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0005\"}}" - }, - { - "description": "[basx047] strings without E cannot generate E in result", - "canonical_bson": "1800000013640005000000000000000000000000003E3000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \".5\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.5\"}}" - }, - { - "description": "[dqbsr431] check rounding modes heeded (Rounded)", - "canonical_bson": "1800000013640099761CC7B548F377DC80A131C836FE2F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.1111111111111111111111111111123450\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.111111111111111111111111111112345\"}}" - }, - { - "description": "OK2", - "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FC2F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \".100000000000000000000000000000000000000000000000000000000000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1000000000000000000000000000000000\"}}" - } - ], - "parseErrors": [ - { - "description": "[basx564] Near-specials (Conversion_syntax)", - "string": "Infi" - }, - { - "description": "[basx565] Near-specials (Conversion_syntax)", - "string": "Infin" - }, - { - "description": "[basx566] Near-specials (Conversion_syntax)", - "string": "Infini" - }, - { - "description": "[basx567] Near-specials (Conversion_syntax)", - "string": "Infinit" - }, - { - "description": "[basx568] Near-specials (Conversion_syntax)", - "string": "-Infinit" - }, - { - "description": "[basx590] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": ".Infinity" - }, - { - "description": "[basx562] Near-specials (Conversion_syntax)", - "string": "NaNq" - }, - { - "description": "[basx563] Near-specials (Conversion_syntax)", - "string": "NaNs" - }, - { - "description": "[dqbas939] overflow results at different rounding modes (Overflow & Inexact & Rounded)", - "string": "-7e10000" - }, - { - "description": "[dqbsr534] negatives (Rounded & Inexact)", - "string": "-1.11111111111111111111111111111234650" - }, - { - "description": "[dqbsr535] negatives (Rounded & Inexact)", - "string": "-1.11111111111111111111111111111234551" - }, - { - "description": "[dqbsr533] negatives (Rounded & Inexact)", - "string": "-1.11111111111111111111111111111234550" - }, - { - "description": "[dqbsr532] negatives (Rounded & Inexact)", - "string": "-1.11111111111111111111111111111234549" - }, - { - "description": "[dqbsr432] check rounding modes heeded (Rounded & Inexact)", - "string": "1.11111111111111111111111111111234549" - }, - { - "description": "[dqbsr433] check rounding modes heeded (Rounded & Inexact)", - "string": "1.11111111111111111111111111111234550" - }, - { - "description": "[dqbsr435] check rounding modes heeded (Rounded & Inexact)", - "string": "1.11111111111111111111111111111234551" - }, - { - "description": "[dqbsr434] check rounding modes heeded (Rounded & Inexact)", - "string": "1.11111111111111111111111111111234650" - }, - { - "description": "[dqbas938] overflow results at different rounding modes (Overflow & Inexact & Rounded)", - "string": "7e10000" - }, - { - "description": "Inexact rounding#1", - "string": "100000000000000000000000000000000000000000000000000000000001" - }, - { - "description": "Inexact rounding#2", - "string": "1E-6177" - } - ] -} diff --git a/bson/src/test/resources/bson/decimal128-5.json b/bson/src/test/resources/bson/decimal128-5.json deleted file mode 100644 index e976eae4075..00000000000 --- a/bson/src/test/resources/bson/decimal128-5.json +++ /dev/null @@ -1,402 +0,0 @@ -{ - "description": "Decimal128", - "bson_type": "0x13", - "test_key": "d", - "valid": [ - { - "description": "[decq035] fold-downs (more below) (Clamped)", - "canonical_bson": "18000000136400000000807F1BCF85B27059C8A43CFE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.23E+6144\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.230000000000000000000000000000000E+6144\"}}" - }, - { - "description": "[decq037] fold-downs (more below) (Clamped)", - "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6144\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+6144\"}}" - }, - { - "description": "[decq077] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04000000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.100000000000000000000000000000000E-6143\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000000E-6144\"}}" - }, - { - "description": "[decq078] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04000000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000000E-6144\"}}" - }, - { - "description": "[decq079] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000A00000000000000000000000000000000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000000000000000000000000000010E-6143\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-6175\"}}" - }, - { - "description": "[decq080] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000A00000000000000000000000000000000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-6175\"}}" - }, - { - "description": "[decq081] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000020000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000000000000000000000000000001E-6143\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6175\"}}" - }, - { - "description": "[decq082] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000020000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6175\"}}" - }, - { - "description": "[decq083] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000000000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000000000000000000000000000001E-6143\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" - }, - { - "description": "[decq084] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000000000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" - }, - { - "description": "[decq090] underflows cannot be tested for simple copies, check edge cases (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000000000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e-6176\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" - }, - { - "description": "[decq100] underflows cannot be tested for simple copies, check edge cases (Subnormal)", - "canonical_bson": "18000000136400FFFFFFFF095BC138938D44C64D31000000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"999999999999999999999999999999999e-6176\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9.99999999999999999999999999999999E-6144\"}}" - }, - { - "description": "[decq130] fold-downs (more below) (Clamped)", - "canonical_bson": "18000000136400000000807F1BCF85B27059C8A43CFEDF00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.23E+6144\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.230000000000000000000000000000000E+6144\"}}" - }, - { - "description": "[decq132] fold-downs (more below) (Clamped)", - "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FEDF00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E+6144\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.000000000000000000000000000000000E+6144\"}}" - }, - { - "description": "[decq177] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04008000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.100000000000000000000000000000000E-6143\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.00000000000000000000000000000000E-6144\"}}" - }, - { - "description": "[decq178] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04008000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.00000000000000000000000000000000E-6144\"}}" - }, - { - "description": "[decq179] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000A00000000000000000000000000008000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000000000000000000000000000010E-6143\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.0E-6175\"}}" - }, - { - "description": "[decq180] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000A00000000000000000000000000008000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.0E-6175\"}}" - }, - { - "description": "[decq181] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000028000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000000000000000000000000000001E-6143\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6175\"}}" - }, - { - "description": "[decq182] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000028000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6175\"}}" - }, - { - "description": "[decq183] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000008000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000000000000000000000000000001E-6143\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6176\"}}" - }, - { - "description": "[decq184] Nmin and below (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000008000", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6176\"}}" - }, - { - "description": "[decq190] underflow edge cases (Subnormal)", - "canonical_bson": "180000001364000100000000000000000000000000008000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1e-6176\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6176\"}}" - }, - { - "description": "[decq200] underflow edge cases (Subnormal)", - "canonical_bson": "18000000136400FFFFFFFF095BC138938D44C64D31008000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-999999999999999999999999999999999e-6176\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.99999999999999999999999999999999E-6144\"}}" - }, - { - "description": "[decq400] zeros (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000000000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-8000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6176\"}}" - }, - { - "description": "[decq401] zeros (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000000000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6177\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6176\"}}" - }, - { - "description": "[decq414] clamped zeros... (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6112\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6111\"}}" - }, - { - "description": "[decq416] clamped zeros... (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6144\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6111\"}}" - }, - { - "description": "[decq418] clamped zeros... (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+8000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6111\"}}" - }, - { - "description": "[decq420] negative zeros (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000008000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-8000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6176\"}}" - }, - { - "description": "[decq421] negative zeros (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000008000", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6177\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6176\"}}" - }, - { - "description": "[decq434] clamped zeros... (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000FEDF00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6112\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6111\"}}" - }, - { - "description": "[decq436] clamped zeros... (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000FEDF00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6144\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6111\"}}" - }, - { - "description": "[decq438] clamped zeros... (Clamped)", - "canonical_bson": "180000001364000000000000000000000000000000FEDF00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+8000\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6111\"}}" - }, - { - "description": "[decq601] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6144\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+6144\"}}" - }, - { - "description": "[decq603] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6143\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000000E+6143\"}}" - }, - { - "description": "[decq605] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640000000080264B91C02220BE377E00FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6142\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000000000E+6142\"}}" - }, - { - "description": "[decq607] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640000000040EAED7446D09C2C9F0C00FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6141\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000E+6141\"}}" - }, - { - "description": "[decq609] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000000A0CA17726DAE0F1E430100FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6140\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000E+6140\"}}" - }, - { - "description": "[decq611] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000000106102253E5ECE4F200000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6139\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000000E+6139\"}}" - }, - { - "description": "[decq613] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000000E83C80D09F3C2E3B030000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6138\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000E+6138\"}}" - }, - { - "description": "[decq615] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000000E4D20CC8DCD2B752000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6137\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000E+6137\"}}" - }, - { - "description": "[decq617] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364000000004A48011416954508000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6136\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000E+6136\"}}" - }, - { - "description": "[decq619] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000000A1EDCCCE1BC2D300000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6135\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000E+6135\"}}" - }, - { - "description": "[decq621] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000080F64AE1C7022D1500000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6134\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000E+6134\"}}" - }, - { - "description": "[decq623] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000040B2BAC9E0191E0200000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6133\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000E+6133\"}}" - }, - { - "description": "[decq625] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364000000A0DEC5ADC935360000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6132\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000E+6132\"}}" - }, - { - "description": "[decq627] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000010632D5EC76B050000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6131\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000E+6131\"}}" - }, - { - "description": "[decq629] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364000000E8890423C78A000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6130\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000E+6130\"}}" - }, - { - "description": "[decq631] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400000064A7B3B6E00D000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6129\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000E+6129\"}}" - }, - { - "description": "[decq633] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640000008A5D78456301000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6128\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000E+6128\"}}" - }, - { - "description": "[decq635] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364000000C16FF2862300000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6127\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000E+6127\"}}" - }, - { - "description": "[decq637] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364000080C6A47E8D0300000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6126\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000E+6126\"}}" - }, - { - "description": "[decq639] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640000407A10F35A0000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6125\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000E+6125\"}}" - }, - { - "description": "[decq641] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640000A0724E18090000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6124\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000E+6124\"}}" - }, - { - "description": "[decq643] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364000010A5D4E8000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6123\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000E+6123\"}}" - }, - { - "description": "[decq645] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640000E8764817000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6122\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000E+6122\"}}" - }, - { - "description": "[decq647] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640000E40B5402000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6121\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000E+6121\"}}" - }, - { - "description": "[decq649] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640000CA9A3B00000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6120\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000E+6120\"}}" - }, - { - "description": "[decq651] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640000E1F50500000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6119\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000E+6119\"}}" - }, - { - "description": "[decq653] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364008096980000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6118\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000E+6118\"}}" - }, - { - "description": "[decq655] fold-down full sequence (Clamped)", - "canonical_bson": "1800000013640040420F0000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6117\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000E+6117\"}}" - }, - { - "description": "[decq657] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400A086010000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6116\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000E+6116\"}}" - }, - { - "description": "[decq659] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364001027000000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6115\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000E+6115\"}}" - }, - { - "description": "[decq661] fold-down full sequence (Clamped)", - "canonical_bson": "18000000136400E803000000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6114\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000E+6114\"}}" - }, - { - "description": "[decq663] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364006400000000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6113\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+6113\"}}" - }, - { - "description": "[decq665] fold-down full sequence (Clamped)", - "canonical_bson": "180000001364000A00000000000000000000000000FE5F00", - "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6112\"}}", - "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+6112\"}}" - } - ] -} - diff --git a/bson/src/test/resources/bson/decimal128-6.json b/bson/src/test/resources/bson/decimal128-6.json deleted file mode 100644 index eba6764e853..00000000000 --- a/bson/src/test/resources/bson/decimal128-6.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "description": "Decimal128", - "bson_type": "0x13", - "test_key": "d", - "parseErrors": [ - { - "description": "Incomplete Exponent", - "string": "1e" - }, - { - "description": "Exponent at the beginning", - "string": "E01" - }, - { - "description": "Just a decimal place", - "string": "." - }, - { - "description": "2 decimal places", - "string": "..3" - }, - { - "description": "2 decimal places", - "string": ".13.3" - }, - { - "description": "2 decimal places", - "string": "1..3" - }, - { - "description": "2 decimal places", - "string": "1.3.4" - }, - { - "description": "2 decimal places", - "string": "1.34." - }, - { - "description": "Decimal with no digits", - "string": ".e" - }, - { - "description": "2 signs", - "string": "+-32.4" - }, - { - "description": "2 signs", - "string": "-+32.4" - }, - { - "description": "2 negative signs", - "string": "--32.4" - }, - { - "description": "2 negative signs", - "string": "-32.-4" - }, - { - "description": "End in negative sign", - "string": "32.0-" - }, - { - "description": "2 negative signs", - "string": "32.4E--21" - }, - { - "description": "2 negative signs", - "string": "32.4E-2-1" - }, - { - "description": "2 signs", - "string": "32.4E+-21" - }, - { - "description": "Empty string", - "string": "" - }, - { - "description": "leading white space positive number", - "string": " 1" - }, - { - "description": "leading white space negative number", - "string": " -1" - }, - { - "description": "trailing white space", - "string": "1 " - }, - { - "description": "Invalid", - "string": "E" - }, - { - "description": "Invalid", - "string": "invalid" - }, - { - "description": "Invalid", - "string": "i" - }, - { - "description": "Invalid", - "string": "in" - }, - { - "description": "Invalid", - "string": "-in" - }, - { - "description": "Invalid", - "string": "Na" - }, - { - "description": "Invalid", - "string": "-Na" - }, - { - "description": "Invalid", - "string": "1.23abc" - }, - { - "description": "Invalid", - "string": "1.23abcE+02" - }, - { - "description": "Invalid", - "string": "1.23E+0aabs2" - } - ] -} diff --git a/bson/src/test/resources/bson/decimal128-7.json b/bson/src/test/resources/bson/decimal128-7.json deleted file mode 100644 index 0b78f1237b8..00000000000 --- a/bson/src/test/resources/bson/decimal128-7.json +++ /dev/null @@ -1,327 +0,0 @@ -{ - "description": "Decimal128", - "bson_type": "0x13", - "test_key": "d", - "parseErrors": [ - { - "description": "[basx572] Near-specials (Conversion_syntax)", - "string": "-9Inf" - }, - { - "description": "[basx516] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "-1-" - }, - { - "description": "[basx533] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "0000.." - }, - { - "description": "[basx534] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": ".0000." - }, - { - "description": "[basx535] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "00..00" - }, - { - "description": "[basx569] Near-specials (Conversion_syntax)", - "string": "0Inf" - }, - { - "description": "[basx571] Near-specials (Conversion_syntax)", - "string": "-0Inf" - }, - { - "description": "[basx575] Near-specials (Conversion_syntax)", - "string": "0sNaN" - }, - { - "description": "[basx503] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "++1" - }, - { - "description": "[basx504] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "--1" - }, - { - "description": "[basx505] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "-+1" - }, - { - "description": "[basx506] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "+-1" - }, - { - "description": "[basx510] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": " +1" - }, - { - "description": "[basx513] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": " + 1" - }, - { - "description": "[basx514] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": " - 1" - }, - { - "description": "[basx501] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "." - }, - { - "description": "[basx502] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": ".." - }, - { - "description": "[basx519] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "" - }, - { - "description": "[basx525] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "e100" - }, - { - "description": "[basx549] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "e+1" - }, - { - "description": "[basx577] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": ".e+1" - }, - { - "description": "[basx578] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "+.e+1" - }, - { - "description": "[basx581] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "E+1" - }, - { - "description": "[basx582] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": ".E+1" - }, - { - "description": "[basx583] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "+.E+1" - }, - { - "description": "[basx579] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "-.e+" - }, - { - "description": "[basx580] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "-.e" - }, - { - "description": "[basx584] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "-.E+" - }, - { - "description": "[basx585] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "-.E" - }, - { - "description": "[basx589] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "+.Inf" - }, - { - "description": "[basx586] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": ".NaN" - }, - { - "description": "[basx587] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "-.NaN" - }, - { - "description": "[basx545] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "ONE" - }, - { - "description": "[basx561] Near-specials (Conversion_syntax)", - "string": "qNaN" - }, - { - "description": "[basx573] Near-specials (Conversion_syntax)", - "string": "-sNa" - }, - { - "description": "[basx588] some baddies with dots and Es and dots and specials (Conversion_syntax)", - "string": "+.sNaN" - }, - { - "description": "[basx544] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "ten" - }, - { - "description": "[basx527] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "u0b65" - }, - { - "description": "[basx526] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "u0e5a" - }, - { - "description": "[basx515] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "x" - }, - { - "description": "[basx574] Near-specials (Conversion_syntax)", - "string": "xNaN" - }, - { - "description": "[basx530] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": ".123.5" - }, - { - "description": "[basx500] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1..2" - }, - { - "description": "[basx542] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1e1.0" - }, - { - "description": "[basx553] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1E+1.2.3" - }, - { - "description": "[basx543] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1e123e" - }, - { - "description": "[basx552] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1E+1.2" - }, - { - "description": "[basx546] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1e.1" - }, - { - "description": "[basx547] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1e1." - }, - { - "description": "[basx554] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1E++1" - }, - { - "description": "[basx555] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1E--1" - }, - { - "description": "[basx556] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1E+-1" - }, - { - "description": "[basx557] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1E-+1" - }, - { - "description": "[basx558] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1E'1" - }, - { - "description": "[basx559] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1E\"1" - }, - { - "description": "[basx520] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1e-" - }, - { - "description": "[basx560] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1E" - }, - { - "description": "[basx548] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1ee" - }, - { - "description": "[basx551] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1.2.1" - }, - { - "description": "[basx550] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1.23.4" - }, - { - "description": "[basx529] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "1.34.5" - }, - { - "description": "[basx531] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "01.35." - }, - { - "description": "[basx532] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "01.35-" - }, - { - "description": "[basx518] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "3+" - }, - { - "description": "[basx521] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "7e99999a" - }, - { - "description": "[basx570] Near-specials (Conversion_syntax)", - "string": "9Inf" - }, - { - "description": "[basx512] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "12 " - }, - { - "description": "[basx517] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "12-" - }, - { - "description": "[basx507] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "12e" - }, - { - "description": "[basx508] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "12e++" - }, - { - "description": "[basx509] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "12f4" - }, - { - "description": "[basx536] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "111e*123" - }, - { - "description": "[basx537] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "111e123-" - }, - { - "description": "[basx540] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "111e1*23" - }, - { - "description": "[basx538] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "111e+12+" - }, - { - "description": "[basx539] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "111e1-3-" - }, - { - "description": "[basx541] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "111E1e+3" - }, - { - "description": "[basx528] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "123,65" - }, - { - "description": "[basx523] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "7e12356789012x" - }, - { - "description": "[basx522] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", - "string": "7e123567890x" - } - ] -} diff --git a/bson/src/test/resources/bson/document.json b/bson/src/test/resources/bson/document.json deleted file mode 100644 index 698e7ae90af..00000000000 --- a/bson/src/test/resources/bson/document.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "description": "Document type (sub-documents)", - "bson_type": "0x03", - "test_key": "x", - "valid": [ - { - "description": "Empty subdoc", - "canonical_bson": "0D000000037800050000000000", - "canonical_extjson": "{\"x\" : {}}" - }, - { - "description": "Empty-string key subdoc", - "canonical_bson": "150000000378000D00000002000200000062000000", - "canonical_extjson": "{\"x\" : {\"\" : \"b\"}}" - }, - { - "description": "Single-character key subdoc", - "canonical_bson": "160000000378000E0000000261000200000062000000", - "canonical_extjson": "{\"x\" : {\"a\" : \"b\"}}" - }, - { - "description": "Dollar-prefixed key in sub-document", - "canonical_bson": "170000000378000F000000022461000200000062000000", - "canonical_extjson": "{\"x\" : {\"$a\" : \"b\"}}" - }, - { - "description": "Dollar as key in sub-document", - "canonical_bson": "160000000378000E0000000224000200000061000000", - "canonical_extjson": "{\"x\" : {\"$\" : \"a\"}}" - }, - { - "description": "Dotted key in sub-document", - "canonical_bson": "180000000378001000000002612E62000200000063000000", - "canonical_extjson": "{\"x\" : {\"a.b\" : \"c\"}}" - }, - { - "description": "Dot as key in sub-document", - "canonical_bson": "160000000378000E000000022E000200000061000000", - "canonical_extjson": "{\"x\" : {\".\" : \"a\"}}" - } - ], - "decodeErrors": [ - { - "description": "Subdocument length too long: eats outer terminator", - "bson": "1800000003666F6F000F0000001062617200FFFFFF7F0000" - }, - { - "description": "Subdocument length too short: leaks terminator", - "bson": "1500000003666F6F000A0000000862617200010000" - }, - { - "description": "Invalid subdocument: bad string length in field", - "bson": "1C00000003666F6F001200000002626172000500000062617A000000" - }, - { - "description": "Null byte in sub-document key", - "bson": "150000000378000D00000010610000010000000000" - } - ] -} diff --git a/bson/src/test/resources/bson/double.json b/bson/src/test/resources/bson/double.json deleted file mode 100644 index 7a3bad158b3..00000000000 --- a/bson/src/test/resources/bson/double.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "description": "Double type", - "bson_type": "0x01", - "test_key": "d", - "valid": [ - { - "description": "+1.0", - "canonical_bson": "10000000016400000000000000F03F00", - "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"1.0\"}}", - "relaxed_extjson": "{\"d\" : 1.0}" - }, - { - "description": "-1.0", - "canonical_bson": "10000000016400000000000000F0BF00", - "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"-1.0\"}}", - "relaxed_extjson": "{\"d\" : -1.0}" - }, - { - "description": "+1.0001220703125", - "canonical_bson": "10000000016400000000008000F03F00", - "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"1.0001220703125\"}}", - "relaxed_extjson": "{\"d\" : 1.0001220703125}" - }, - { - "description": "-1.0001220703125", - "canonical_bson": "10000000016400000000008000F0BF00", - "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"-1.0001220703125\"}}", - "relaxed_extjson": "{\"d\" : -1.0001220703125}" - }, - { - "description": "1.2345678921232E18", - "canonical_bson": "100000000164002a1bf5f41022b14300", - "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"1.2345678921232E18\"}}", - "relaxed_extjson": "{\"d\" : 1.2345678921232E18}" - }, - { - "description": "-1.2345678921232E18", - "canonical_bson": "100000000164002a1bf5f41022b1c300", - "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"-1.2345678921232E18\"}}", - "relaxed_extjson": "{\"d\" : -1.2345678921232E18}" - }, - { - "description": "0.0", - "canonical_bson": "10000000016400000000000000000000", - "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"0.0\"}}", - "relaxed_extjson": "{\"d\" : 0.0}" - }, - { - "description": "-0.0", - "canonical_bson": "10000000016400000000000000008000", - "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"-0.0\"}}", - "relaxed_extjson": "{\"d\" : -0.0}" - }, - { - "description": "NaN", - "canonical_bson": "10000000016400000000000000F87F00", - "canonical_extjson": "{\"d\": {\"$numberDouble\": \"NaN\"}}", - "relaxed_extjson": "{\"d\": {\"$numberDouble\": \"NaN\"}}", - "lossy": true - }, - { - "description": "NaN with payload", - "canonical_bson": "10000000016400120000000000F87F00", - "canonical_extjson": "{\"d\": {\"$numberDouble\": \"NaN\"}}", - "relaxed_extjson": "{\"d\": {\"$numberDouble\": \"NaN\"}}", - "lossy": true - }, - { - "description": "Inf", - "canonical_bson": "10000000016400000000000000F07F00", - "canonical_extjson": "{\"d\": {\"$numberDouble\": \"Infinity\"}}", - "relaxed_extjson": "{\"d\": {\"$numberDouble\": \"Infinity\"}}" - }, - { - "description": "-Inf", - "canonical_bson": "10000000016400000000000000F0FF00", - "canonical_extjson": "{\"d\": {\"$numberDouble\": \"-Infinity\"}}", - "relaxed_extjson": "{\"d\": {\"$numberDouble\": \"-Infinity\"}}" - } - ], - "decodeErrors": [ - { - "description": "double truncated", - "bson": "0B0000000164000000F03F00" - } - ] -} diff --git a/bson/src/test/resources/bson/int32.json b/bson/src/test/resources/bson/int32.json deleted file mode 100644 index 1353fc3df8b..00000000000 --- a/bson/src/test/resources/bson/int32.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Int32 type", - "bson_type": "0x10", - "test_key": "i", - "valid": [ - { - "description": "MinValue", - "canonical_bson": "0C0000001069000000008000", - "canonical_extjson": "{\"i\" : {\"$numberInt\": \"-2147483648\"}}", - "relaxed_extjson": "{\"i\" : -2147483648}" - }, - { - "description": "MaxValue", - "canonical_bson": "0C000000106900FFFFFF7F00", - "canonical_extjson": "{\"i\" : {\"$numberInt\": \"2147483647\"}}", - "relaxed_extjson": "{\"i\" : 2147483647}" - }, - { - "description": "-1", - "canonical_bson": "0C000000106900FFFFFFFF00", - "canonical_extjson": "{\"i\" : {\"$numberInt\": \"-1\"}}", - "relaxed_extjson": "{\"i\" : -1}" - }, - { - "description": "0", - "canonical_bson": "0C0000001069000000000000", - "canonical_extjson": "{\"i\" : {\"$numberInt\": \"0\"}}", - "relaxed_extjson": "{\"i\" : 0}" - }, - { - "description": "1", - "canonical_bson": "0C0000001069000100000000", - "canonical_extjson": "{\"i\" : {\"$numberInt\": \"1\"}}", - "relaxed_extjson": "{\"i\" : 1}" - } - ], - "decodeErrors": [ - { - "description": "Bad int32 field length", - "bson": "090000001061000500" - } - ] -} diff --git a/bson/src/test/resources/bson/int64.json b/bson/src/test/resources/bson/int64.json deleted file mode 100644 index 91f4abff950..00000000000 --- a/bson/src/test/resources/bson/int64.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Int64 type", - "bson_type": "0x12", - "test_key": "a", - "valid": [ - { - "description": "MinValue", - "canonical_bson": "10000000126100000000000000008000", - "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"-9223372036854775808\"}}", - "relaxed_extjson": "{\"a\" : -9223372036854775808}" - }, - { - "description": "MaxValue", - "canonical_bson": "10000000126100FFFFFFFFFFFFFF7F00", - "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"9223372036854775807\"}}", - "relaxed_extjson": "{\"a\" : 9223372036854775807}" - }, - { - "description": "-1", - "canonical_bson": "10000000126100FFFFFFFFFFFFFFFF00", - "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"-1\"}}", - "relaxed_extjson": "{\"a\" : -1}" - }, - { - "description": "0", - "canonical_bson": "10000000126100000000000000000000", - "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"0\"}}", - "relaxed_extjson": "{\"a\" : 0}" - }, - { - "description": "1", - "canonical_bson": "10000000126100010000000000000000", - "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"1\"}}", - "relaxed_extjson": "{\"a\" : 1}" - } - ], - "decodeErrors": [ - { - "description": "int64 field truncated", - "bson": "0C0000001261001234567800" - } - ] -} diff --git a/bson/src/test/resources/bson/maxkey.json b/bson/src/test/resources/bson/maxkey.json deleted file mode 100644 index 67cad6db57b..00000000000 --- a/bson/src/test/resources/bson/maxkey.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "description": "Maxkey type", - "bson_type": "0x7F", - "test_key": "a", - "valid": [ - { - "description": "Maxkey", - "canonical_bson": "080000007F610000", - "canonical_extjson": "{\"a\" : {\"$maxKey\" : 1}}" - } - ] -} diff --git a/bson/src/test/resources/bson/minkey.json b/bson/src/test/resources/bson/minkey.json deleted file mode 100644 index 8adee4509a5..00000000000 --- a/bson/src/test/resources/bson/minkey.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "description": "Minkey type", - "bson_type": "0xFF", - "test_key": "a", - "valid": [ - { - "description": "Minkey", - "canonical_bson": "08000000FF610000", - "canonical_extjson": "{\"a\" : {\"$minKey\" : 1}}" - } - ] -} diff --git a/bson/src/test/resources/bson/multi-type-deprecated.json b/bson/src/test/resources/bson/multi-type-deprecated.json deleted file mode 100644 index 665f388cd41..00000000000 --- a/bson/src/test/resources/bson/multi-type-deprecated.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "description": "Multiple types within the same document", - "bson_type": "0x00", - "deprecated": true, - "valid": [ - { - "description": "All BSON types", - "canonical_bson": "38020000075F69640057E193D7A9CC81B4027498B50E53796D626F6C000700000073796D626F6C0002537472696E670007000000737472696E670010496E743332002A00000012496E743634002A0000000000000001446F75626C6500000000000000F0BF0542696E617279001000000003A34C38F7C3ABEDC8A37814A992AB8DB60542696E61727955736572446566696E656400050000008001020304050D436F6465000E00000066756E6374696F6E2829207B7D000F436F64655769746853636F7065001B0000000E00000066756E6374696F6E2829207B7D00050000000003537562646F63756D656E74001200000002666F6F0004000000626172000004417272617900280000001030000100000010310002000000103200030000001033000400000010340005000000001154696D657374616D7000010000002A0000000B5265676578007061747465726E0000094461746574696D6545706F6368000000000000000000094461746574696D65506F73697469766500FFFFFF7F00000000094461746574696D654E656761746976650000000080FFFFFFFF085472756500010846616C736500000C4442506F696E746572000B000000636F6C6C656374696F6E0057E193D7A9CC81B4027498B1034442526566003D0000000224726566000B000000636F6C6C656374696F6E00072469640057FD71E96E32AB4225B723FB02246462000900000064617461626173650000FF4D696E6B6579007F4D61786B6579000A4E756C6C0006556E646566696E65640000", - "converted_bson": "48020000075f69640057e193d7a9cc81b4027498b50253796d626f6c000700000073796d626f6c0002537472696e670007000000737472696e670010496e743332002a00000012496e743634002a0000000000000001446f75626c6500000000000000f0bf0542696e617279001000000003a34c38f7c3abedc8a37814a992ab8db60542696e61727955736572446566696e656400050000008001020304050d436f6465000e00000066756e6374696f6e2829207b7d000f436f64655769746853636f7065001b0000000e00000066756e6374696f6e2829207b7d00050000000003537562646f63756d656e74001200000002666f6f0004000000626172000004417272617900280000001030000100000010310002000000103200030000001033000400000010340005000000001154696d657374616d7000010000002a0000000b5265676578007061747465726e0000094461746574696d6545706f6368000000000000000000094461746574696d65506f73697469766500ffffff7f00000000094461746574696d654e656761746976650000000080ffffffff085472756500010846616c73650000034442506f696e746572002b0000000224726566000b000000636f6c6c656374696f6e00072469640057e193d7a9cc81b4027498b100034442526566003d0000000224726566000b000000636f6c6c656374696f6e00072469640057fd71e96e32ab4225b723fb02246462000900000064617461626173650000ff4d696e6b6579007f4d61786b6579000a4e756c6c000a556e646566696e65640000", - "canonical_extjson": "{\"_id\": {\"$oid\": \"57e193d7a9cc81b4027498b5\"}, \"Symbol\": {\"$symbol\": \"symbol\"}, \"String\": \"string\", \"Int32\": {\"$numberInt\": \"42\"}, \"Int64\": {\"$numberLong\": \"42\"}, \"Double\": {\"$numberDouble\": \"-1.0\"}, \"Binary\": { \"$binary\" : {\"base64\": \"o0w498Or7cijeBSpkquNtg==\", \"subType\": \"03\"}}, \"BinaryUserDefined\": { \"$binary\" : {\"base64\": \"AQIDBAU=\", \"subType\": \"80\"}}, \"Code\": {\"$code\": \"function() {}\"}, \"CodeWithScope\": {\"$code\": \"function() {}\", \"$scope\": {}}, \"Subdocument\": {\"foo\": \"bar\"}, \"Array\": [{\"$numberInt\": \"1\"}, {\"$numberInt\": \"2\"}, {\"$numberInt\": \"3\"}, {\"$numberInt\": \"4\"}, {\"$numberInt\": \"5\"}], \"Timestamp\": {\"$timestamp\": {\"t\": 42, \"i\": 1}}, \"Regex\": {\"$regularExpression\": {\"pattern\": \"pattern\", \"options\": \"\"}}, \"DatetimeEpoch\": {\"$date\": {\"$numberLong\": \"0\"}}, \"DatetimePositive\": {\"$date\": {\"$numberLong\": \"2147483647\"}}, \"DatetimeNegative\": {\"$date\": {\"$numberLong\": \"-2147483648\"}}, \"True\": true, \"False\": false, \"DBPointer\": {\"$dbPointer\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"57e193d7a9cc81b4027498b1\"}}}, \"DBRef\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"57fd71e96e32ab4225b723fb\"}, \"$db\": \"database\"}, \"Minkey\": {\"$minKey\": 1}, \"Maxkey\": {\"$maxKey\": 1}, \"Null\": null, \"Undefined\": {\"$undefined\": true}}", - "converted_extjson": "{\"_id\": {\"$oid\": \"57e193d7a9cc81b4027498b5\"}, \"Symbol\": \"symbol\", \"String\": \"string\", \"Int32\": {\"$numberInt\": \"42\"}, \"Int64\": {\"$numberLong\": \"42\"}, \"Double\": {\"$numberDouble\": \"-1.0\"}, \"Binary\": { \"$binary\" : {\"base64\": \"o0w498Or7cijeBSpkquNtg==\", \"subType\": \"03\"}}, \"BinaryUserDefined\": { \"$binary\" : {\"base64\": \"AQIDBAU=\", \"subType\": \"80\"}}, \"Code\": {\"$code\": \"function() {}\"}, \"CodeWithScope\": {\"$code\": \"function() {}\", \"$scope\": {}}, \"Subdocument\": {\"foo\": \"bar\"}, \"Array\": [{\"$numberInt\": \"1\"}, {\"$numberInt\": \"2\"}, {\"$numberInt\": \"3\"}, {\"$numberInt\": \"4\"}, {\"$numberInt\": \"5\"}], \"Timestamp\": {\"$timestamp\": {\"t\": 42, \"i\": 1}}, \"Regex\": {\"$regularExpression\": {\"pattern\": \"pattern\", \"options\": \"\"}}, \"DatetimeEpoch\": {\"$date\": {\"$numberLong\": \"0\"}}, \"DatetimePositive\": {\"$date\": {\"$numberLong\": \"2147483647\"}}, \"DatetimeNegative\": {\"$date\": {\"$numberLong\": \"-2147483648\"}}, \"True\": true, \"False\": false, \"DBPointer\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"57e193d7a9cc81b4027498b1\"}}, \"DBRef\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"57fd71e96e32ab4225b723fb\"}, \"$db\": \"database\"}, \"Minkey\": {\"$minKey\": 1}, \"Maxkey\": {\"$maxKey\": 1}, \"Null\": null, \"Undefined\": null}" - } - ] -} - diff --git a/bson/src/test/resources/bson/multi-type.json b/bson/src/test/resources/bson/multi-type.json deleted file mode 100644 index 1e1d557c9ba..00000000000 --- a/bson/src/test/resources/bson/multi-type.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "Multiple types within the same document", - "bson_type": "0x00", - "valid": [ - { - "description": "All BSON types", - "canonical_bson": "F4010000075F69640057E193D7A9CC81B4027498B502537472696E670007000000737472696E670010496E743332002A00000012496E743634002A0000000000000001446F75626C6500000000000000F0BF0542696E617279001000000003A34C38F7C3ABEDC8A37814A992AB8DB60542696E61727955736572446566696E656400050000008001020304050D436F6465000E00000066756E6374696F6E2829207B7D000F436F64655769746853636F7065001B0000000E00000066756E6374696F6E2829207B7D00050000000003537562646F63756D656E74001200000002666F6F0004000000626172000004417272617900280000001030000100000010310002000000103200030000001033000400000010340005000000001154696D657374616D7000010000002A0000000B5265676578007061747465726E0000094461746574696D6545706F6368000000000000000000094461746574696D65506F73697469766500FFFFFF7F00000000094461746574696D654E656761746976650000000080FFFFFFFF085472756500010846616C73650000034442526566003D0000000224726566000B000000636F6C6C656374696F6E00072469640057FD71E96E32AB4225B723FB02246462000900000064617461626173650000FF4D696E6B6579007F4D61786B6579000A4E756C6C0000", - "canonical_extjson": "{\"_id\": {\"$oid\": \"57e193d7a9cc81b4027498b5\"}, \"String\": \"string\", \"Int32\": {\"$numberInt\": \"42\"}, \"Int64\": {\"$numberLong\": \"42\"}, \"Double\": {\"$numberDouble\": \"-1.0\"}, \"Binary\": { \"$binary\" : {\"base64\": \"o0w498Or7cijeBSpkquNtg==\", \"subType\": \"03\"}}, \"BinaryUserDefined\": { \"$binary\" : {\"base64\": \"AQIDBAU=\", \"subType\": \"80\"}}, \"Code\": {\"$code\": \"function() {}\"}, \"CodeWithScope\": {\"$code\": \"function() {}\", \"$scope\": {}}, \"Subdocument\": {\"foo\": \"bar\"}, \"Array\": [{\"$numberInt\": \"1\"}, {\"$numberInt\": \"2\"}, {\"$numberInt\": \"3\"}, {\"$numberInt\": \"4\"}, {\"$numberInt\": \"5\"}], \"Timestamp\": {\"$timestamp\": {\"t\": 42, \"i\": 1}}, \"Regex\": {\"$regularExpression\": {\"pattern\": \"pattern\", \"options\": \"\"}}, \"DatetimeEpoch\": {\"$date\": {\"$numberLong\": \"0\"}}, \"DatetimePositive\": {\"$date\": {\"$numberLong\": \"2147483647\"}}, \"DatetimeNegative\": {\"$date\": {\"$numberLong\": \"-2147483648\"}}, \"True\": true, \"False\": false, \"DBRef\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"57fd71e96e32ab4225b723fb\"}, \"$db\": \"database\"}, \"Minkey\": {\"$minKey\": 1}, \"Maxkey\": {\"$maxKey\": 1}, \"Null\": null}" - } - ] -} diff --git a/bson/src/test/resources/bson/null.json b/bson/src/test/resources/bson/null.json deleted file mode 100644 index f9b269473e6..00000000000 --- a/bson/src/test/resources/bson/null.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "description": "Null type", - "bson_type": "0x0A", - "test_key": "a", - "valid": [ - { - "description": "Null", - "canonical_bson": "080000000A610000", - "canonical_extjson": "{\"a\" : null}" - } - ] -} diff --git a/bson/src/test/resources/bson/oid.json b/bson/src/test/resources/bson/oid.json deleted file mode 100644 index 14e9caf4b40..00000000000 --- a/bson/src/test/resources/bson/oid.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "ObjectId", - "bson_type": "0x07", - "test_key": "a", - "valid": [ - { - "description": "All zeroes", - "canonical_bson": "1400000007610000000000000000000000000000", - "canonical_extjson": "{\"a\" : {\"$oid\" : \"000000000000000000000000\"}}" - }, - { - "description": "All ones", - "canonical_bson": "14000000076100FFFFFFFFFFFFFFFFFFFFFFFF00", - "canonical_extjson": "{\"a\" : {\"$oid\" : \"ffffffffffffffffffffffff\"}}" - }, - { - "description": "Random", - "canonical_bson": "1400000007610056E1FC72E0C917E9C471416100", - "canonical_extjson": "{\"a\" : {\"$oid\" : \"56e1fc72e0c917e9c4714161\"}}" - } - ], - "decodeErrors": [ - { - "description": "OID truncated", - "bson": "1200000007610056E1FC72E0C917E9C471" - } - ] -} diff --git a/bson/src/test/resources/bson/regex.json b/bson/src/test/resources/bson/regex.json deleted file mode 100644 index 223802169df..00000000000 --- a/bson/src/test/resources/bson/regex.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "description": "Regular Expression type", - "bson_type": "0x0B", - "test_key": "a", - "valid": [ - { - "description": "empty regex with no options", - "canonical_bson": "0A0000000B6100000000", - "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"\", \"options\" : \"\"}}}" - }, - { - "description": "regex without options", - "canonical_bson": "0D0000000B6100616263000000", - "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"\"}}}" - }, - { - "description": "regex with options", - "canonical_bson": "0F0000000B610061626300696D0000", - "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"im\"}}}" - }, - { - "description": "regex with options (keys reversed)", - "canonical_bson": "0F0000000B610061626300696D0000", - "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"im\"}}}", - "degenerate_extjson": "{\"a\" : {\"$regularExpression\" : {\"options\" : \"im\", \"pattern\": \"abc\"}}}" - }, - { - "description": "regex with slash", - "canonical_bson": "110000000B610061622F636400696D0000", - "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"ab/cd\", \"options\" : \"im\"}}}" - }, - { - "description": "flags not alphabetized", - "degenerate_bson": "100000000B6100616263006D69780000", - "canonical_bson": "100000000B610061626300696D780000", - "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"imx\"}}}", - "degenerate_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"mix\"}}}" - }, - { - "description" : "Required escapes", - "canonical_bson" : "100000000B610061625C226162000000", - "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"ab\\\\\\\"ab\", \"options\" : \"\"}}}" - }, - { - "description" : "Regular expression as value of $regex query operator", - "canonical_bson" : "180000000B247265676578007061747465726E0069780000", - "canonical_extjson": "{\"$regex\" : {\"$regularExpression\" : { \"pattern\": \"pattern\", \"options\" : \"ix\"}}}" - }, - { - "description" : "Regular expression as value of $regex query operator with $options", - "canonical_bson" : "270000000B247265676578007061747465726E000002246F7074696F6E73000300000069780000", - "canonical_extjson": "{\"$regex\" : {\"$regularExpression\" : { \"pattern\": \"pattern\", \"options\" : \"\"}}, \"$options\" : \"ix\"}" - } - ], - "decodeErrors": [ - { - "description": "Null byte in pattern string", - "bson": "0F0000000B610061006300696D0000" - }, - { - "description": "Null byte in flags string", - "bson": "100000000B61006162630069006D0000" - } - ] -} diff --git a/bson/src/test/resources/bson/string.json b/bson/src/test/resources/bson/string.json deleted file mode 100644 index 148334d0919..00000000000 --- a/bson/src/test/resources/bson/string.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "description": "String", - "bson_type": "0x02", - "test_key": "a", - "valid": [ - { - "description": "Empty string", - "canonical_bson": "0D000000026100010000000000", - "canonical_extjson": "{\"a\" : \"\"}" - }, - { - "description": "Single character", - "canonical_bson": "0E00000002610002000000620000", - "canonical_extjson": "{\"a\" : \"b\"}" - }, - { - "description": "Multi-character", - "canonical_bson": "190000000261000D0000006162616261626162616261620000", - "canonical_extjson": "{\"a\" : \"abababababab\"}" - }, - { - "description": "two-byte UTF-8 (\u00e9)", - "canonical_bson": "190000000261000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", - "canonical_extjson": "{\"a\" : \"\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\"}" - }, - { - "description": "three-byte UTF-8 (\u2606)", - "canonical_bson": "190000000261000D000000E29886E29886E29886E298860000", - "canonical_extjson": "{\"a\" : \"\\u2606\\u2606\\u2606\\u2606\"}" - }, - { - "description": "Embedded nulls", - "canonical_bson": "190000000261000D0000006162006261620062616261620000", - "canonical_extjson": "{\"a\" : \"ab\\u0000bab\\u0000babab\"}" - }, - { - "description": "Required escapes", - "canonical_bson" : "320000000261002600000061625C220102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F61620000", - "canonical_extjson" : "{\"a\":\"ab\\\\\\\"\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001fab\"}" - } - ], - "decodeErrors": [ - { - "description": "bad string length: 0 (but no 0x00 either)", - "bson": "0C0000000261000000000000" - }, - { - "description": "bad string length: -1", - "bson": "0C000000026100FFFFFFFF00" - }, - { - "description": "bad string length: eats terminator", - "bson": "10000000026100050000006200620000" - }, - { - "description": "bad string length: longer than rest of document", - "bson": "120000000200FFFFFF00666F6F6261720000" - }, - { - "description": "string is not null-terminated", - "bson": "1000000002610004000000616263FF00" - }, - { - "description": "empty string, but extra null", - "bson": "0E00000002610001000000000000" - }, - { - "description": "invalid UTF-8", - "bson": "0E00000002610002000000E90000" - } - ] -} diff --git a/bson/src/test/resources/bson/symbol.json b/bson/src/test/resources/bson/symbol.json deleted file mode 100644 index 3dd3577ebd1..00000000000 --- a/bson/src/test/resources/bson/symbol.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "description": "Symbol", - "bson_type": "0x0E", - "deprecated": true, - "test_key": "a", - "valid": [ - { - "description": "Empty string", - "canonical_bson": "0D0000000E6100010000000000", - "canonical_extjson": "{\"a\": {\"$symbol\": \"\"}}", - "converted_bson": "0D000000026100010000000000", - "converted_extjson": "{\"a\": \"\"}" - }, - { - "description": "Single character", - "canonical_bson": "0E0000000E610002000000620000", - "canonical_extjson": "{\"a\": {\"$symbol\": \"b\"}}", - "converted_bson": "0E00000002610002000000620000", - "converted_extjson": "{\"a\": \"b\"}" - }, - { - "description": "Multi-character", - "canonical_bson": "190000000E61000D0000006162616261626162616261620000", - "canonical_extjson": "{\"a\": {\"$symbol\": \"abababababab\"}}", - "converted_bson": "190000000261000D0000006162616261626162616261620000", - "converted_extjson": "{\"a\": \"abababababab\"}" - }, - { - "description": "two-byte UTF-8 (\u00e9)", - "canonical_bson": "190000000E61000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", - "canonical_extjson": "{\"a\": {\"$symbol\": \"éééééé\"}}", - "converted_bson": "190000000261000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", - "converted_extjson": "{\"a\": \"éééééé\"}" - }, - { - "description": "three-byte UTF-8 (\u2606)", - "canonical_bson": "190000000E61000D000000E29886E29886E29886E298860000", - "canonical_extjson": "{\"a\": {\"$symbol\": \"☆☆☆☆\"}}", - "converted_bson": "190000000261000D000000E29886E29886E29886E298860000", - "converted_extjson": "{\"a\": \"☆☆☆☆\"}" - }, - { - "description": "Embedded nulls", - "canonical_bson": "190000000E61000D0000006162006261620062616261620000", - "canonical_extjson": "{\"a\": {\"$symbol\": \"ab\\u0000bab\\u0000babab\"}}", - "converted_bson": "190000000261000D0000006162006261620062616261620000", - "converted_extjson": "{\"a\": \"ab\\u0000bab\\u0000babab\"}" - } - ], - "decodeErrors": [ - { - "description": "bad symbol length: 0 (but no 0x00 either)", - "bson": "0C0000000E61000000000000" - }, - { - "description": "bad symbol length: -1", - "bson": "0C0000000E6100FFFFFFFF00" - }, - { - "description": "bad symbol length: eats terminator", - "bson": "100000000E6100050000006200620000" - }, - { - "description": "bad symbol length: longer than rest of document", - "bson": "120000000E00FFFFFF00666F6F6261720000" - }, - { - "description": "symbol is not null-terminated", - "bson": "100000000E610004000000616263FF00" - }, - { - "description": "empty symbol, but extra null", - "bson": "0E0000000E610001000000000000" - }, - { - "description": "invalid UTF-8", - "bson": "0E0000000E610002000000E90000" - } - ] -} diff --git a/bson/src/test/resources/bson/timestamp.json b/bson/src/test/resources/bson/timestamp.json deleted file mode 100644 index 6f46564a327..00000000000 --- a/bson/src/test/resources/bson/timestamp.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "description": "Timestamp type", - "bson_type": "0x11", - "test_key": "a", - "valid": [ - { - "description": "Timestamp: (123456789, 42)", - "canonical_bson": "100000001161002A00000015CD5B0700", - "canonical_extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 123456789, \"i\" : 42} } }" - }, - { - "description": "Timestamp: (123456789, 42) (keys reversed)", - "canonical_bson": "100000001161002A00000015CD5B0700", - "canonical_extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 123456789, \"i\" : 42} } }", - "degenerate_extjson": "{\"a\" : {\"$timestamp\" : {\"i\" : 42, \"t\" : 123456789} } }" - }, - { - "description": "Timestamp with high-order bit set on both seconds and increment", - "canonical_bson": "10000000116100FFFFFFFFFFFFFFFF00", - "canonical_extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 4294967295, \"i\" : 4294967295} } }" - }, - { - "description": "Timestamp with high-order bit set on both seconds and increment (not UINT32_MAX)", - "canonical_bson": "1000000011610000286BEE00286BEE00", - "canonical_extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 4000000000, \"i\" : 4000000000} } }" - } - ], - "decodeErrors": [ - { - "description": "Truncated timestamp field", - "bson": "0f0000001161002A00000015CD5B00" - } - ] -} diff --git a/bson/src/test/resources/bson/top.json b/bson/src/test/resources/bson/top.json deleted file mode 100644 index 9c649b5e3f0..00000000000 --- a/bson/src/test/resources/bson/top.json +++ /dev/null @@ -1,266 +0,0 @@ -{ - "description": "Top-level document validity", - "bson_type": "0x00", - "valid": [ - { - "description": "Dollar-prefixed key in top-level document", - "canonical_bson": "0F00000010246B6579002A00000000", - "canonical_extjson": "{\"$key\": {\"$numberInt\": \"42\"}}" - }, - { - "description": "Dollar as key in top-level document", - "canonical_bson": "0E00000002240002000000610000", - "canonical_extjson": "{\"$\": \"a\"}" - }, - { - "description": "Dotted key in top-level document", - "canonical_bson": "1000000002612E620002000000630000", - "canonical_extjson": "{\"a.b\": \"c\"}" - }, - { - "description": "Dot as key in top-level document", - "canonical_bson": "0E000000022E0002000000610000", - "canonical_extjson": "{\".\": \"a\"}" - } - ], - "decodeErrors": [ - { - "description": "An object size that's too small to even include the object size, but is a well-formed, empty object", - "bson": "0100000000" - }, - { - "description": "An object size that's only enough for the object size, but is a well-formed, empty object", - "bson": "0400000000" - }, - { - "description": "One object, with length shorter than size (missing EOO)", - "bson": "05000000" - }, - { - "description": "One object, sized correctly, with a spot for an EOO, but the EOO is 0x01", - "bson": "0500000001" - }, - { - "description": "One object, sized correctly, with a spot for an EOO, but the EOO is 0xff", - "bson": "05000000FF" - }, - { - "description": "One object, sized correctly, with a spot for an EOO, but the EOO is 0x70", - "bson": "0500000070" - }, - { - "description": "Byte count is zero (with non-zero input length)", - "bson": "00000000000000000000" - }, - { - "description": "Stated length exceeds byte count, with truncated document", - "bson": "1200000002666F6F0004000000626172" - }, - { - "description": "Stated length less than byte count, with garbage after envelope", - "bson": "1200000002666F6F00040000006261720000DEADBEEF" - }, - { - "description": "Stated length exceeds byte count, with valid envelope", - "bson": "1300000002666F6F00040000006261720000" - }, - { - "description": "Stated length less than byte count, with valid envelope", - "bson": "1100000002666F6F00040000006261720000" - }, - { - "description": "Invalid BSON type low range", - "bson": "07000000000000" - }, - { - "description": "Invalid BSON type high range", - "bson": "07000000800000" - }, - { - "description": "Document truncated mid-key", - "bson": "1200000002666F" - }, - { - "description": "Null byte in document key", - "bson": "0D000000107800000100000000" - } - ], - "parseErrors": [ - { - "description" : "Bad $regularExpression (extra field)", - "string" : "{\"a\" : {\"$regularExpression\": {\"pattern\": \"abc\", \"options\": \"\", \"unrelated\": true}}}" - }, - { - "description" : "Bad $regularExpression (missing options field)", - "string" : "{\"a\" : {\"$regularExpression\": {\"pattern\": \"abc\"}}}" - }, - { - "description": "Bad $regularExpression (pattern is number, not string)", - "string": "{\"x\" : {\"$regularExpression\" : { \"pattern\": 42, \"options\" : \"\"}}}" - }, - { - "description": "Bad $regularExpression (options are number, not string)", - "string": "{\"x\" : {\"$regularExpression\" : { \"pattern\": \"a\", \"options\" : 0}}}" - }, - { - "description" : "Bad $regularExpression (missing pattern field)", - "string" : "{\"a\" : {\"$regularExpression\": {\"options\":\"ix\"}}}" - }, - { - "description": "Bad $oid (number, not string)", - "string": "{\"a\" : {\"$oid\" : 42}}" - }, - { - "description": "Bad $oid (extra field)", - "string": "{\"a\" : {\"$oid\" : \"56e1fc72e0c917e9c4714161\", \"unrelated\": true}}" - }, - { - "description": "Bad $numberInt (number, not string)", - "string": "{\"a\" : {\"$numberInt\" : 42}}" - }, - { - "description": "Bad $numberInt (extra field)", - "string": "{\"a\" : {\"$numberInt\" : \"42\", \"unrelated\": true}}" - }, - { - "description": "Bad $numberLong (number, not string)", - "string": "{\"a\" : {\"$numberLong\" : 42}}" - }, - { - "description": "Bad $numberLong (extra field)", - "string": "{\"a\" : {\"$numberLong\" : \"42\", \"unrelated\": true}}" - }, - { - "description": "Bad $numberDouble (number, not string)", - "string": "{\"a\" : {\"$numberDouble\" : 42}}" - }, - { - "description": "Bad $numberDouble (extra field)", - "string": "{\"a\" : {\"$numberDouble\" : \".1\", \"unrelated\": true}}" - }, - { - "description": "Bad $numberDecimal (number, not string)", - "string": "{\"a\" : {\"$numberDecimal\" : 42}}" - }, - { - "description": "Bad $numberDecimal (extra field)", - "string": "{\"a\" : {\"$numberDecimal\" : \".1\", \"unrelated\": true}}" - }, - { - "description": "Bad $binary (binary is number, not string)", - "string": "{\"x\" : {\"$binary\" : {\"base64\" : 0, \"subType\" : \"00\"}}}" - }, - { - "description": "Bad $binary (type is number, not string)", - "string": "{\"x\" : {\"$binary\" : {\"base64\" : \"\", \"subType\" : 0}}}" - }, - { - "description": "Bad $binary (missing $type)", - "string": "{\"x\" : {\"$binary\" : {\"base64\" : \"//8=\"}}}" - }, - { - "description": "Bad $binary (missing $binary)", - "string": "{\"x\" : {\"$binary\" : {\"subType\" : \"00\"}}}" - }, - { - "description": "Bad $binary (extra field)", - "string": "{\"x\" : {\"$binary\" : {\"base64\" : \"//8=\", \"subType\" : 0, \"unrelated\": true}}}" - }, - { - "description": "Bad $code (type is number, not string)", - "string": "{\"a\" : {\"$code\" : 42}}" - }, - { - "description": "Bad $code (type is number, not string) when $scope is also present", - "string": "{\"a\" : {\"$code\" : 42, \"$scope\" : {}}}" - }, - { - "description": "Bad $code (extra field)", - "string": "{\"a\" : {\"$code\" : \"\", \"unrelated\": true}}" - }, - { - "description": "Bad $code with $scope (scope is number, not doc)", - "string": "{\"x\" : {\"$code\" : \"\", \"$scope\" : 42}}" - }, - { - "description": "Bad $timestamp (type is number, not doc)", - "string": "{\"a\" : {\"$timestamp\" : 42} }" - }, - { - "description": "Bad $timestamp ('t' type is string, not number)", - "string": "{\"a\" : {\"$timestamp\" : {\"t\" : \"123456789\", \"i\" : 42} } }" - }, - { - "description": "Bad $timestamp ('i' type is string, not number)", - "string": "{\"a\" : {\"$timestamp\" : {\"t\" : 123456789, \"i\" : \"42\"} } }" - }, - { - "description": "Bad $timestamp (extra field at same level as $timestamp)", - "string": "{\"a\" : {\"$timestamp\" : {\"t\" : \"123456789\", \"i\" : \"42\"}, \"unrelated\": true } }" - }, - { - "description": "Bad $timestamp (extra field at same level as t and i)", - "string": "{\"a\" : {\"$timestamp\" : {\"t\" : \"123456789\", \"i\" : \"42\", \"unrelated\": true} } }" - }, - { - "description": "Bad $timestamp (missing t)", - "string": "{\"a\" : {\"$timestamp\" : {\"i\" : \"42\"} } }" - }, - { - "description": "Bad $timestamp (missing i)", - "string": "{\"a\" : {\"$timestamp\" : {\"t\" : \"123456789\"} } }" - }, - { - "description": "Bad $date (number, not string or hash)", - "string": "{\"a\" : {\"$date\" : 42}}" - }, - { - "description": "Bad $date (extra field)", - "string": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"1356351330501\"}, \"unrelated\": true}}" - }, - { - "description": "Bad $minKey (boolean, not integer)", - "string": "{\"a\" : {\"$minKey\" : true}}" - }, - { - "description": "Bad $minKey (wrong integer)", - "string": "{\"a\" : {\"$minKey\" : 0}}" - }, - { - "description": "Bad $minKey (extra field)", - "string": "{\"a\" : {\"$minKey\" : 1, \"unrelated\": true}}" - }, - { - "description": "Bad $maxKey (boolean, not integer)", - "string": "{\"a\" : {\"$maxKey\" : true}}" - }, - { - "description": "Bad $maxKey (wrong integer)", - "string": "{\"a\" : {\"$maxKey\" : 0}}" - }, - { - "description": "Bad $maxKey (extra field)", - "string": "{\"a\" : {\"$maxKey\" : 1, \"unrelated\": true}}" - }, - { - "description": "Bad DBpointer (extra field)", - "string": "{\"a\": {\"$dbPointer\": {\"a\": {\"$numberInt\": \"1\"}, \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}, \"c\": {\"$numberInt\": \"2\"}, \"$ref\": \"b\"}}}" - }, - { - "description" : "Null byte in document key", - "string" : "{\"a\\u0000\": 1 }" - }, - { - "description" : "Null byte in sub-document key", - "string" : "{\"a\" : {\"b\\u0000\": 1 }}" - }, - { - "description": "Null byte in $regularExpression pattern", - "string": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"b\\u0000\", \"options\" : \"i\"}}}" - }, - { - "description": "Null byte in $regularExpression options", - "string": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"b\", \"options\" : \"i\\u0000\"}}}" - } - ] -} diff --git a/bson/src/test/resources/bson/undefined.json b/bson/src/test/resources/bson/undefined.json deleted file mode 100644 index 285f068258c..00000000000 --- a/bson/src/test/resources/bson/undefined.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "description": "Undefined type (deprecated)", - "bson_type": "0x06", - "deprecated": true, - "test_key": "a", - "valid": [ - { - "description": "Undefined", - "canonical_bson": "0800000006610000", - "canonical_extjson": "{\"a\" : {\"$undefined\" : true}}", - "converted_bson": "080000000A610000", - "converted_extjson": "{\"a\" : null}" - } - ] -} diff --git a/bson/src/test/unit/org/bson/DocumentTest.java b/bson/src/test/unit/org/bson/DocumentTest.java index bd9551e9407..089f182f387 100644 --- a/bson/src/test/unit/org/bson/DocumentTest.java +++ b/bson/src/test/unit/org/bson/DocumentTest.java @@ -151,6 +151,16 @@ public void toJsonShouldRenderUuidAsStandard() { assertEquals(new BsonDocument("_id", new BsonBinary(uuid)), BsonDocument.parse(json)); } + @Test + public void parseShouldHandleDoubleSignedExponent() { + BsonDocument expected = new BsonDocument("d", new BsonDouble(1.2345678921232E+18)); + assertEquals(expected, BsonDocument.parse("{\"d\" : {\"$numberDouble\": \"1.2345678921232E18\"}}"), "implicit positive exponent"); + assertEquals(expected, BsonDocument.parse("{\"d\" : {\"$numberDouble\": \"1.2345678921232E+18\"}}"), "explicit positive exponent"); + + expected = new BsonDocument("d", new BsonDouble(1.2345678921232E-18)); + assertEquals(expected, BsonDocument.parse("{\"d\" : {\"$numberDouble\": \"1.2345678921232E-18\"}}"), "explicit negative exponent"); + } + public class Name { private final String name; diff --git a/bson/src/test/unit/org/bson/GenericBsonTest.java b/bson/src/test/unit/org/bson/GenericBsonTest.java index 582ec5d83dc..8fa06b8a484 100644 --- a/bson/src/test/unit/org/bson/GenericBsonTest.java +++ b/bson/src/test/unit/org/bson/GenericBsonTest.java @@ -283,7 +283,7 @@ private void throwIfValueIsStringContainingReplacementCharacter(final BsonDocume private static Stream data() { List data = new ArrayList<>(); - for (BsonDocument testDocument : JsonPoweredTestHelper.getTestDocuments("/bson")) { + for (BsonDocument testDocument : JsonPoweredTestHelper.getSpecTestDocuments("bson-corpus")) { for (BsonValue curValue : testDocument.getArray("valid", new BsonArray())) { BsonDocument testCaseDocument = curValue.asDocument(); data.add(Arguments.of( diff --git a/bson/src/test/unit/org/bson/json/JsonWriterTest.java b/bson/src/test/unit/org/bson/json/JsonWriterTest.java index 00777a3dfec..3cde9248adc 100644 --- a/bson/src/test/unit/org/bson/json/JsonWriterTest.java +++ b/bson/src/test/unit/org/bson/json/JsonWriterTest.java @@ -259,19 +259,19 @@ public void testBoolean() { public void testDouble() { List> tests = asList(new TestData<>(0.0, "0.0"), new TestData<>(0.0005, "5.0E-4"), new TestData<>(0.5, "0.5"), new TestData<>(1.0, "1.0"), - new TestData<>(1.5, "1.5"), new TestData<>(1.5E+40, "1.5E40"), + new TestData<>(1.5, "1.5"), new TestData<>(1.5E40, "1.5E+40"), new TestData<>(1.5E-40, "1.5E-40"), - new TestData<>(1234567890.1234568E+123, "1.2345678901234568E132"), - new TestData<>(Double.MAX_VALUE, "1.7976931348623157E308"), + new TestData<>(1234567890.1234568E+123, "1.2345678901234568E+132"), + new TestData<>(Double.MAX_VALUE, "1.7976931348623157E+308"), new TestData<>(Double.MIN_VALUE, "4.9E-324"), new TestData<>(-0.0005, "-5.0E-4"), new TestData<>(-0.5, "-0.5"), new TestData<>(-1.0, "-1.0"), new TestData<>(-1.5, "-1.5"), - new TestData<>(-1.5E+40, "-1.5E40"), + new TestData<>(-1.5E+40, "-1.5E+40"), new TestData<>(-1.5E-40, "-1.5E-40"), - new TestData<>(-1234567890.1234568E+123, "-1.2345678901234568E132"), + new TestData<>(-1234567890.1234568E+123, "-1.2345678901234568E+132"), new TestData<>(Double.NaN, "NaN"), new TestData<>(Double.NEGATIVE_INFINITY, "-Infinity"), diff --git a/bson/src/test/unit/org/bson/BinaryVectorTest.java b/bson/src/test/unit/org/bson/vector/BinaryVectorProseTest.java similarity index 64% rename from bson/src/test/unit/org/bson/BinaryVectorTest.java rename to bson/src/test/unit/org/bson/vector/BinaryVectorProseTest.java index 57e8b294019..e0643732063 100644 --- a/bson/src/test/unit/org/bson/BinaryVectorTest.java +++ b/bson/src/test/unit/org/bson/vector/BinaryVectorProseTest.java @@ -14,19 +14,110 @@ * limitations under the License. */ -package org.bson; - +package org.bson.vector; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import org.bson.BinaryVector; +import org.bson.BsonBinary; +import org.bson.Float32BinaryVector; +import org.bson.Int8BinaryVector; +import org.bson.PackedBitBinaryVector; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.slf4j.LoggerFactory; 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.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -class BinaryVectorTest { +// See Prose Tests README: https://github.com/mongodb/specifications/tree/master/source/bson-binary-vector/tests#prose-tests +class BinaryVectorProseTest { + + private ListAppender logWatcher; + + @BeforeEach + void setup() { + logWatcher = new ListAppender<>(); + logWatcher.start(); + ((Logger) LoggerFactory.getLogger("org.bson.BinaryVector")).addAppender(logWatcher); + } + + @AfterEach + void teardown() { + ((Logger) LoggerFactory.getLogger("org.bson.BinaryVector")).detachAndStopAllAppenders(); + } + + @DisplayName("Treatment of non-zero ignored bits: 1. Encoding") + @Test + void shouldEncodeWithNonZeroIgnoredBits() { + // when + byte[] data = {(byte) 0b11111111}; + + // then + Assertions.assertDoesNotThrow(()-> BinaryVector.packedBitVector(data, (byte) 7)); + ILoggingEvent iLoggingEvent = logWatcher.list.get(0); + assertEquals(Level.WARN, iLoggingEvent.getLevel()); + assertEquals("The last 7 padded bits should be zero in the final byte.", iLoggingEvent.getMessage()); + } + + @DisplayName("Treatment of non-zero ignored bits: 2. Decoding") + @Test + void decodingWithNonZeroIgnoredBits() { + // when + byte[] bytearray = {0x10, 0x07, (byte) 0xFF}; + BsonBinary data = new BsonBinary((byte) 9, bytearray); + + // then + assertDoesNotThrow(data::asVector); + ILoggingEvent iLoggingEvent = logWatcher.list.get(0); + assertEquals(Level.WARN, iLoggingEvent.getLevel()); + assertEquals("The last 7 padded bits should be zero in the final byte.", iLoggingEvent.getMessage()); + } + + @DisplayName("Treatment of non-zero ignored bits: 3. Comparison") + @Test + void shouldCompareVectorsWithIgnoredBits() { + // b1: 1-bit vector, all 0 ignored bits + byte[] b1Bytes = {0x10, 0x07, (byte) 0x80}; + BsonBinary b1 = new BsonBinary((byte) 9, b1Bytes); + + // b2: 1-bit vector, all 1 ignored bits + byte[] b2Bytes = {0x10, 0x07, (byte) 0xFF}; + BsonBinary b2 = new BsonBinary((byte) 9, b2Bytes); + + // b3: same data as b1, constructed from vector + PackedBitBinaryVector vector = BinaryVector.packedBitVector(new byte[]{(byte) 0x80}, (byte) 7); + BsonBinary b3 = new BsonBinary(vector); + + // Vector representations + BinaryVector v1 = b1.asVector(); + BinaryVector v2 = b2.asVector(); + BinaryVector v3 = b3.asVector(); + + // Raw binary equality + assertNotEquals(b1, b2); // Unequal at naive Binary level + assertEquals(b1, b3); // Equal at naive Binary level + + // Vector equality + assertNotEquals(v2, v1); // Unequal at BinaryVector level ([255] != [128]) + assertEquals(v1, v3); // Equal at BinaryVector level + } + + // + // Extra non specification based tests + // @Test void shouldCreateInt8Vector() { // given diff --git a/bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java b/bson/src/test/unit/org/bson/vector/BinaryVectorTest.java similarity index 78% rename from bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java rename to bson/src/test/unit/org/bson/vector/BinaryVectorTest.java index 35326281c66..73188358b67 100644 --- a/bson/src/test/unit/org/bson/vector/BinaryVectorGenericBsonTest.java +++ b/bson/src/test/unit/org/bson/vector/BinaryVectorTest.java @@ -20,7 +20,7 @@ import org.bson.BsonArray; import org.bson.BsonBinary; import org.bson.BsonDocument; -import org.bson.BsonString; +import org.bson.BsonInt32; import org.bson.BsonValue; import org.bson.Float32BinaryVector; import org.bson.PackedBitBinaryVector; @@ -28,6 +28,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentest4j.AssertionFailedError; import util.JsonPoweredTestHelper; import java.util.ArrayList; @@ -36,44 +37,47 @@ import java.util.stream.Stream; import static java.lang.String.format; +import static java.util.Arrays.asList; import static org.bson.BsonHelper.decodeToDocument; import static org.bson.BsonHelper.encodeToHex; 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; import static org.junit.jupiter.api.Assumptions.assumeFalse; /** * See * JSON-based tests that included in test resources. */ -class BinaryVectorGenericBsonTest { +class BinaryVectorTest { - 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. + private static final List TEST_NAMES_TO_IGNORE = asList( + // 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", + // It is impossible to overflow byte with values higher than 127 in the API. + "Overflow Vector PACKED_BIT", + // It is impossible to underflow byte with values lower than -128 in the API. "Underflow Vector PACKED_BIT", - //It is impossible to provide float inputs for PACKED_BIT in the API. + // It is impossible to provide float inputs for INT8 in the API. + "INT8 with float inputs", + // 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"); - + // It is impossible to provide float inputs with padding for FLOAT32 in the API. + "FLOAT32 with padding", + // It is impossible to provide padding for INT8 in the API. + "INT8 with padding", + // TODO JAVA-5848 in 6.0.0 "Padding specified with no vector data PACKED_BIT" will throw an error (currently logs a warning). + "Padding specified with no vector data PACKED_BIT" +); @ParameterizedTest(name = "{0}") @MethodSource("data") 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 testDescription = testCase.get("description").asString().getValue(); + assumeFalse(TEST_NAMES_TO_IGNORE.contains(testDescription)); String testKey = testDefinition.getString("test_key").getValue(); boolean isValidVector = testCase.getBoolean("valid").getValue(); @@ -85,29 +89,35 @@ void shouldPassAllOutcomes(@SuppressWarnings("unused") final String description, } private static void runInvalidTestCase(final BsonDocument testCase) { + if (testCase.containsKey("vector")) { + runInvalidTestCaseVector(testCase); + } + } + + private static void runInvalidTestCaseVector(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); - } + assertThrows(IllegalArgumentException.class, () -> { + switch (expectedDType) { + case INT8: + byte[] expectedVectorData = toByteArray(arrayVector); + BinaryVector.int8Vector(expectedVectorData); + break; + case PACKED_BIT: + byte expectedPadding = (byte) testCase.getInt32("padding", new BsonInt32(0)).getValue(); + byte[] expectedVectorPackedBitData = toByteArray(arrayVector); + BinaryVector.packedBitVector(expectedVectorPackedBitData, expectedPadding); + break; + case FLOAT32: + float[] expectedFloatVector = toFloatArray(arrayVector); + BinaryVector.floatVector(expectedFloatVector); + break; + default: + throw new AssertionFailedError("Unsupported vector data type: " + expectedDType); + } + }); } private static void runValidTestCase(final String testKey, final BsonDocument testCase) { @@ -168,14 +178,10 @@ private static void runValidTestCase(final String testKey, final BsonDocument te description); break; default: - throw new IllegalArgumentException("Unsupported vector data type: " + expectedDType); + throw new AssertionFailedError("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, @@ -231,7 +237,7 @@ private static float[] toFloatArray(final BsonArray arrayVector) { for (int i = 0; i < arrayVector.size(); i++) { BsonValue bsonValue = arrayVector.get(i); if (bsonValue.isString()) { - floats[i] = parseFloat(bsonValue.asString()); + floats[i] = Float.parseFloat(bsonValue.asString().getValue()); } else { floats[i] = (float) arrayVector.get(i).asDouble().getValue(); } @@ -239,21 +245,9 @@ private static float[] toFloatArray(final BsonArray arrayVector) { 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 data() { List data = new ArrayList<>(); - for (BsonDocument testDocument : JsonPoweredTestHelper.getTestDocuments("/bson-binary-vector")) { + for (BsonDocument testDocument : JsonPoweredTestHelper.getSpecTestDocuments("bson-binary-vector")) { for (BsonValue curValue : testDocument.getArray("tests", new BsonArray())) { BsonDocument testCaseDocument = curValue.asDocument(); data.add(Arguments.of(createTestCaseDescription(testDocument, testCaseDocument), testDocument, testCaseDocument)); diff --git a/driver-core/build.gradle.kts b/driver-core/build.gradle.kts index 9a0faaf8dff..282c478858d 100644 --- a/driver-core/build.gradle.kts +++ b/driver-core/build.gradle.kts @@ -66,6 +66,11 @@ dependencies { } } +tasks.processTestResources { + from("${rootProject.projectDir}/testing/resources") + into("${layout.buildDirectory.get()}/resources/test") +} + configureMavenPublication { pom { name.set("MongoDB Java Driver Core") diff --git a/driver-core/src/test/resources/specifications b/driver-core/src/test/resources/specifications deleted file mode 160000 index a8d34be0df2..00000000000 --- a/driver-core/src/test/resources/specifications +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a8d34be0df234365600a9269af5a463f581562fd diff --git a/driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java b/driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java index 5ac15e92817..8b878fa77c5 100644 --- a/driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java +++ b/driver-core/src/test/unit/com/mongodb/connection/ServerSelectionSelectionTest.java @@ -49,7 +49,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; +import static org.junit.Assume.assumeFalse; // See https://github.com/mongodb/specifications/tree/master/source/server-selection/tests @RunWith(Parameterized.class) @@ -72,7 +72,9 @@ public ServerSelectionSelectionTest(final String description, final BsonDocument @Test public void shouldPassAllOutcomes() { // skip this test because the driver prohibits maxStaleness or tagSets with mode of primary at a much lower level - assumeTrue(!description.endsWith("/max-staleness/tests/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.json")); + assumeFalse(description.endsWith("/max-staleness/tests/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.json")); + assumeFalse(description.contains("Deprioritized")); // TODO JAVA-6021 deprioritized server selection" + ServerSelector serverSelector = null; List suitableServers = buildServerDescriptions(definition.getArray("suitable_servers", new BsonArray())); List selectedServers = null; diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index e63cf1490c4..2225f837ec5 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -421,9 +421,13 @@ public static void applyCustomizations(final TestDef def) { def.skipJira("https://jira.mongodb.org/browse/JAVA-5664") .file("server-discovery-and-monitoring", "pool-cleared-on-min-pool-size-population-error"); def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-error-fail"); + .file("server-discovery-and-monitoring", "backpressure-network-error-fail-single"); def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") - .file("server-discovery-and-monitoring", "backpressure-network-timeout-error"); + .file("server-discovery-and-monitoring", "backpressure-network-timeout-error-single"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") + .file("server-discovery-and-monitoring", "backpressure-network-error-fail-replicaset"); + def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") + .file("server-discovery-and-monitoring", "backpressure-network-timeout-error-replicaset"); def.skipJira("https://jira.mongodb.org/browse/JAVA-5949") .file("server-discovery-and-monitoring", "backpressure-server-description-unchanged-on-min-pool-size-population-error"); diff --git a/driver-core/src/test/resources/logback-test.xml b/testing/resources/logback-test.xml similarity index 100% rename from driver-core/src/test/resources/logback-test.xml rename to testing/resources/logback-test.xml diff --git a/testing/resources/specifications b/testing/resources/specifications new file mode 160000 index 00000000000..de684cf1ef9 --- /dev/null +++ b/testing/resources/specifications @@ -0,0 +1 @@ +Subproject commit de684cf1ef9feede71d358cbb7d253840f1a8647 From 475aa6c471f42b7af6097172dbeb3773468fe269 Mon Sep 17 00:00:00 2001 From: Almas Abdrazak Date: Mon, 26 Jan 2026 15:57:26 -0800 Subject: [PATCH 45/60] generate sarif (#1869) Co-authored-by: Almas Abdrazak --- .evergreen/ssdlc-report.sh | 4 ++++ .../main/kotlin/conventions/spotbugs.gradle.kts | 15 +++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.evergreen/ssdlc-report.sh b/.evergreen/ssdlc-report.sh index 56d5957f5ab..76b3322b147 100755 --- a/.evergreen/ssdlc-report.sh +++ b/.evergreen/ssdlc-report.sh @@ -71,6 +71,10 @@ printf "\nSpotBugs created the following SARIF reports\n" IFS=$'\n' declare -a SARIF_PATHS=($(find "${RELATIVE_DIR_PATH}/.." -path "*/spotbugs/*.sarif")) unset IFS +if [ ${#SARIF_PATHS[@]} -eq 0 ]; then + printf "\nERROR: No SARIF files found matching pattern */spotbugs/*.sarif\n" + exit 1 +fi for SARIF_PATH in "${SARIF_PATHS[@]}"; do GRADLE_PROJECT_NAME="$(basename "$(dirname "$(dirname "$(dirname "$(dirname "${SARIF_PATH}")")")")")" NEW_SARIF_PATH="${SSDLC_STATIC_ANALYSIS_REPORTS_PATH}/${GRADLE_PROJECT_NAME}_$(basename "${SARIF_PATH}")" diff --git a/buildSrc/src/main/kotlin/conventions/spotbugs.gradle.kts b/buildSrc/src/main/kotlin/conventions/spotbugs.gradle.kts index e7ea096fc33..224c0aa4832 100644 --- a/buildSrc/src/main/kotlin/conventions/spotbugs.gradle.kts +++ b/buildSrc/src/main/kotlin/conventions/spotbugs.gradle.kts @@ -40,10 +40,17 @@ spotbugs { tasks.withType().configureEach { if (name == "spotbugsMain") { - reports { - register("xml") { required.set(project.buildingWith("xmlReports.enabled")) } - register("html") { required.set(!project.buildingWith("xmlReports.enabled")) } - register("sarif") { required.set(project.buildingWith("ssdlcReport.enabled")) } + reports.create("xml") { + required.set(project.buildingWith("xmlReports.enabled")) + outputLocation.set(file("$buildDir/reports/spotbugs/spotbugs.xml")) + } + reports.create("html") { + required.set(!project.buildingWith("xmlReports.enabled")) + outputLocation.set(file("$buildDir/reports/spotbugs/spotbugs.html")) + } + reports.create("sarif") { + required.set(project.buildingWith("ssdlcReport.enabled")) + outputLocation.set(file("$buildDir/reports/spotbugs/spotbugs.sarif")) } } else if (name == "spotbugsTest") { enabled = false From 4cea337cccaf243c2642d33c18e55fcc52ab75bd Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Mon, 26 Jan 2026 04:36:28 -0700 Subject: [PATCH 46/60] Fix/improve ClientSessionImpl, remove ClientSessionClock --- .../com/mongodb/internal/TimeoutContext.java | 10 +-- .../com/mongodb/internal/connection/Time.java | 4 ++ .../com/mongodb/internal/time/StartTime.java | 2 +- .../mongodb/internal/time/SystemNanoTime.java | 32 +++++++++ .../com/mongodb/internal/time/TimePoint.java | 4 +- .../client/internal/ClientSessionClock.java | 41 ----------- .../client/internal/ClientSessionImpl.java | 37 +++++----- .../ClientSideOperationTimeoutTest.java | 1 - .../client/WithTransactionProseTest.java | 71 +++++++++++-------- 9 files changed, 107 insertions(+), 95 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/internal/time/SystemNanoTime.java delete mode 100644 driver-sync/src/main/com/mongodb/client/internal/ClientSessionClock.java diff --git a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java index 838c5208807..ee61eacf7d4 100644 --- a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java +++ b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java @@ -168,6 +168,10 @@ public Timeout timeoutIncludingRoundTrip() { return timeout == null ? null : timeout.shortenBy(minRoundTripTimeMS, MILLISECONDS); } + public Timeout timeoutOrAlternative(final Timeout alternative) { + return timeout == null ? alternative : timeout; + } + /** * Returns the remaining {@code timeoutMS} if set or the {@code alternativeTimeoutMS}. * @@ -176,6 +180,7 @@ public Timeout timeoutIncludingRoundTrip() { * @param alternativeTimeoutMS the alternative timeout. * @return timeout to use. */ + @VisibleForTesting(otherwise = PRIVATE) public long timeoutOrAlternative(final long alternativeTimeoutMS) { if (timeout == null) { return alternativeTimeoutMS; @@ -380,11 +385,6 @@ public TimeoutContext withAdditionalReadTimeout(final int additionalReadTimeout) return new TimeoutContext(timeoutSettings.withReadTimeoutMS(newReadTimeout > 0 ? newReadTimeout : Long.MAX_VALUE)); } - // Creates a copy of the timeout context that can be reset without resetting the original. - public TimeoutContext copyTimeoutContext() { - return new TimeoutContext(getTimeoutSettings(), getTimeout()); - } - @Override public String toString() { return "TimeoutContext{" diff --git a/driver-core/src/main/com/mongodb/internal/connection/Time.java b/driver-core/src/main/com/mongodb/internal/connection/Time.java index e3940adf1de..9b7f935e631 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/Time.java +++ b/driver-core/src/main/com/mongodb/internal/connection/Time.java @@ -20,7 +20,11 @@ * To enable unit testing of classes that rely on System.nanoTime * *

This class is not part of the public API and may be removed or changed at any time

+ * + * @deprecated Use {@link com.mongodb.internal.time.SystemNanoTime} in production code, + * and {@code Mockito.mockStatic} in test code to tamper with it. */ +@Deprecated public final class Time { static final long CONSTANT_TIME = 42; diff --git a/driver-core/src/main/com/mongodb/internal/time/StartTime.java b/driver-core/src/main/com/mongodb/internal/time/StartTime.java index 1d8f186ab67..650f9a0ebb9 100644 --- a/driver-core/src/main/com/mongodb/internal/time/StartTime.java +++ b/driver-core/src/main/com/mongodb/internal/time/StartTime.java @@ -59,6 +59,6 @@ public interface StartTime { * @return a StartPoint, as of now */ static StartTime now() { - return TimePoint.at(System.nanoTime()); + return TimePoint.at(SystemNanoTime.get()); } } diff --git a/driver-core/src/main/com/mongodb/internal/time/SystemNanoTime.java b/driver-core/src/main/com/mongodb/internal/time/SystemNanoTime.java new file mode 100644 index 00000000000..f047108d509 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/time/SystemNanoTime.java @@ -0,0 +1,32 @@ +/* + * 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.internal.time; + +/** + * Avoid using this class directly and prefer using other program elements from {@link com.mongodb.internal.time}, if possible. + *

+ * We do not use {@link System#nanoTime()} directly in the rest of the {@link com.mongodb.internal.time} package, + * and use {@link SystemNanoTime#get()} instead because we need to tamper with it via {@code Mockito.mockStatic}, + * and mocking methods of {@link System} class is both impossible and unwise. + */ +public final class SystemNanoTime { + private SystemNanoTime() { + } + + public static long get() { + return System.nanoTime(); + } +} diff --git a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java index 811065d13a6..c3b130e584d 100644 --- a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java +++ b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java @@ -61,14 +61,14 @@ static TimePoint at(@Nullable final Long nanos) { @VisibleForTesting(otherwise = PRIVATE) long currentNanos() { - return System.nanoTime(); + return SystemNanoTime.get(); } /** * Returns the current {@link TimePoint}. */ static TimePoint now() { - return at(System.nanoTime()); + return at(SystemNanoTime.get()); } /** diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionClock.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionClock.java deleted file mode 100644 index a5ba63e3cd6..00000000000 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionClock.java +++ /dev/null @@ -1,41 +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.internal; - -/** - *

This class is not part of the public API and may be removed or changed at any time

- */ -public final class ClientSessionClock { - public static final ClientSessionClock INSTANCE = new ClientSessionClock(0L); - - private long currentTime; - - private ClientSessionClock(final long millis) { - currentTime = millis; - } - - public long now() { - if (currentTime == 0L) { - return System.currentTimeMillis(); - } - return currentTime; - } - - public void setTime(final long millis) { - currentTime = millis; - } -} diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index 03ef3248c36..693df7dc7fa 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -39,8 +39,12 @@ import com.mongodb.internal.session.BaseClientSessionImpl; import com.mongodb.internal.session.ServerSessionPool; import com.mongodb.internal.time.ExponentialBackoff; +import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; + import static com.mongodb.MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL; import static com.mongodb.MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL; import static com.mongodb.assertions.Assertions.assertNotNull; @@ -51,7 +55,7 @@ final class ClientSessionImpl extends BaseClientSessionImpl implements ClientSession { - private static final int MAX_RETRY_TIME_LIMIT_MS = 120000; + private static final long MAX_RETRY_TIME_LIMIT_MS = 120000; private final OperationExecutor operationExecutor; private TransactionState transactionState = TransactionState.NONE; @@ -251,9 +255,11 @@ public T withTransaction(final TransactionBody transactionBody) { @Override public T withTransaction(final TransactionBody transactionBody, final TransactionOptions options) { notNull("transactionBody", transactionBody); - long startTime = ClientSessionClock.INSTANCE.now(); TimeoutContext withTransactionTimeoutContext = createTimeoutContext(options); - ExponentialBackoff transactionBackoff = ExponentialBackoff.TRANSACTION; + Timeout withTransactionTimeout = withTransactionTimeoutContext.timeoutOrAlternative( + assertNotNull(TimeoutContext.startTimeout(MAX_RETRY_TIME_LIMIT_MS))); + BooleanSupplier withTransactionTimeoutExpired = () -> withTransactionTimeout.call(TimeUnit.MILLISECONDS, + () -> false, ms -> false, () -> true); int transactionAttempt = 0; MongoException lastError = null; @@ -261,11 +267,11 @@ public T withTransaction(final TransactionBody transactionBody, final Tra outer: while (true) { if (transactionAttempt > 0) { - backoff(transactionBackoff, transactionAttempt, startTime, lastError); + backoff(transactionAttempt, withTransactionTimeout, assertNotNull(lastError)); } T retVal; try { - startTransaction(options, withTransactionTimeoutContext.copyTimeoutContext()); + startTransaction(options, withTransactionTimeoutContext); transactionAttempt++; if (transactionSpan != null) { @@ -281,7 +287,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra if (!(e instanceof MongoOperationTimeoutException)) { MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e); if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL) - && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { + && !withTransactionTimeoutExpired.getAsBoolean()) { if (transactionSpan != null) { transactionSpan.spanFinalizing(false); } @@ -297,9 +303,10 @@ public T withTransaction(final TransactionBody transactionBody, final Tra commitTransaction(false); break; } catch (MongoException e) { + lastError = e; clearTransactionContextOnError(e); if (!(e instanceof MongoOperationTimeoutException) - && ClientSessionClock.INSTANCE.now() - startTime < MAX_RETRY_TIME_LIMIT_MS) { + && !withTransactionTimeoutExpired.getAsBoolean()) { applyMajorityWriteConcernToTransactionOptions(); if (!(e instanceof MongoExecutionTimeoutException) @@ -309,7 +316,6 @@ public T withTransaction(final TransactionBody transactionBody, final Tra if (transactionSpan != null) { transactionSpan.spanFinalizing(true); } - lastError = e; continue outer; } } @@ -374,15 +380,12 @@ private TimeoutContext createTimeoutContext(final TransactionOptions transaction operationExecutor.getTimeoutSettings())); } - private static void backoff(final ExponentialBackoff exponentialBackoff, final int transactionAttempt, final long startTime, - final MongoException lastError) { - long backoffMs = exponentialBackoff.calculateDelayBeforeNextRetryMs(transactionAttempt - 1); - if (ClientSessionClock.INSTANCE.now() + backoffMs - startTime >= MAX_RETRY_TIME_LIMIT_MS) { - if (lastError != null) { - throw lastError; - } - throw new MongoClientException("Transaction retry timeout exceeded"); - } + private static void backoff(final int transactionAttempt, + final Timeout withTransactionTimeout, final MongoException lastError) { + long backoffMs = ExponentialBackoff.TRANSACTION.calculateDelayBeforeNextRetryMs(transactionAttempt - 1); + withTransactionTimeout.shortenBy(backoffMs, TimeUnit.MILLISECONDS).onExpired(() -> { + throw lastError; + }); try { if (backoffMs > 0) { Thread.sleep(backoffMs); diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java index cb62545f4e4..9fc2f0e6acc 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java @@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assumptions.assumeFalse; - // See https://github.com/mongodb/specifications/tree/master/source/client-side-operation-timeout/tests public class ClientSideOperationTimeoutTest extends UnifiedSyncTest { diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index e2dce11583f..514dbc4a2e6 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -20,17 +20,22 @@ import com.mongodb.MongoClientException; import com.mongodb.MongoException; import com.mongodb.TransactionOptions; -import com.mongodb.client.internal.ClientSessionClock; import com.mongodb.client.model.Sorts; import com.mongodb.internal.time.ExponentialBackoff; +import com.mongodb.internal.time.StartTime; +import com.mongodb.internal.time.SystemNanoTime; import org.bson.BsonDocument; import org.bson.Document; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import static com.mongodb.ClusterFixture.TIMEOUT; import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet; @@ -44,8 +49,7 @@ // See https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/tests/README.md#prose-tests public class WithTransactionProseTest extends DatabaseTestCase { - private static final long START_TIME_MS = 1L; - private static final long ERROR_GENERATING_INTERVAL = 121000L; + private static final Duration ERROR_GENERATING_INTERVAL = Duration.ofSeconds(120); @BeforeEach @Override @@ -66,7 +70,7 @@ public void setUp() { public void testCallbackRaisesCustomError() { final String exceptionMessage = "NotTransientOrUnknownError"; try (ClientSession session = client.startSession()) { - session.withTransaction((TransactionBody) () -> { + session.withTransaction(() -> { throw new MongoException(exceptionMessage); }); // should not get here @@ -101,13 +105,13 @@ public void testRetryTimeoutEnforcedTransientTransactionError() { final String errorMessage = "transient transaction error"; try (ClientSession session = client.startSession()) { - ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction((TransactionBody) () -> { - ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); - MongoException e = new MongoException(112, errorMessage); - e.addLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL); - throw e; - }); + doWithSystemNanoTimeHandle(systemNanoTimeHandle -> + session.withTransaction(() -> { + systemNanoTimeHandle.setRelativeToStart(ERROR_GENERATING_INTERVAL); + MongoException e = new MongoException(112, errorMessage); + e.addLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL); + throw e; + })); fail("Test should have thrown an exception."); } catch (Exception e) { assertEquals(errorMessage, e.getMessage()); @@ -127,12 +131,12 @@ public void testRetryTimeoutEnforcedUnknownTransactionCommit() { + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 91, 'closeConnection': false}}")); try (ClientSession session = client.startSession()) { - ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction((TransactionBody) () -> { - ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); - collection.insertOne(session, new Document("_id", 2)); - return null; - }); + doWithSystemNanoTimeHandle(systemNanoTimeHandle -> + session.withTransaction(() -> { + systemNanoTimeHandle.setRelativeToStart(ERROR_GENERATING_INTERVAL); + collection.insertOne(session, new Document("_id", 2)); + return null; + })); fail("Test should have thrown an exception."); } catch (Exception e) { assertEquals(91, ((MongoException) e).getCode()); @@ -156,12 +160,12 @@ public void testRetryTimeoutEnforcedTransientTransactionErrorOnCommit() { + "'errmsg': 'Transaction 0 has been aborted', 'closeConnection': false}}")); try (ClientSession session = client.startSession()) { - ClientSessionClock.INSTANCE.setTime(START_TIME_MS); - session.withTransaction((TransactionBody) () -> { - ClientSessionClock.INSTANCE.setTime(ERROR_GENERATING_INTERVAL); - collection.insertOne(session, Document.parse("{ _id : 1 }")); - return null; - }); + doWithSystemNanoTimeHandle(systemNanoTimeHandle -> + session.withTransaction(() -> { + systemNanoTimeHandle.setRelativeToStart(ERROR_GENERATING_INTERVAL); + collection.insertOne(session, Document.parse("{ _id : 1 }")); + return null; + })); fail("Test should have thrown an exception."); } catch (Exception e) { assertEquals(251, ((MongoException) e).getCode()); @@ -224,9 +228,9 @@ public void testRetryBackoffIsEnforced() throws InterruptedException { long noBackoffTime; try (ClientSession session = client.startSession(); FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { - long startNanos = System.nanoTime(); + StartTime startTime = StartTime.now(); session.withTransaction(() -> collection.insertOne(session, Document.parse("{}"))); - noBackoffTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos); + noBackoffTime = startTime.elapsed().toMillis(); } finally { // Clear the test jitter supplier to avoid affecting other tests ExponentialBackoff.clearTestJitterSupplier(); @@ -241,9 +245,9 @@ public void testRetryBackoffIsEnforced() throws InterruptedException { long withBackoffTime; try (ClientSession session = client.startSession(); FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { - long startNanos = System.nanoTime(); + StartTime startTime = StartTime.now(); session.withTransaction(() -> collection.insertOne(session, Document.parse("{}"))); - withBackoffTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos); + withBackoffTime = startTime.elapsed().toMillis(); } finally { ExponentialBackoff.clearTestJitterSupplier(); } @@ -278,7 +282,18 @@ public void testExponentialBackoffOnTransientError() throws InterruptedException } } - private boolean canRunTests() { + private static boolean canRunTests() { return isSharded() || isDiscoverableReplicaSet(); } + + private static void doWithSystemNanoTimeHandle(final Consumer action) { + long startNanos = SystemNanoTime.get(); + try (MockedStatic mockedStaticSystem = Mockito.mockStatic(SystemNanoTime.class)) { + action.accept(change -> mockedStaticSystem.when(SystemNanoTime::get).thenReturn(startNanos + change.toNanos())); + } + } + + private interface SystemNanoTimeHandle { + void setRelativeToStart(Duration change); + } } From 8bc1de26ec47041ea3c974e72b2144444ec7430f Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 29 Jan 2026 00:03:33 +0000 Subject: [PATCH 47/60] PR feedback --- .../internal/time/ExponentialBackoff.java | 42 +++++--------- .../internal/ExponentialBackoffTest.java | 55 +++++++++++-------- .../client/internal/ClientSessionImpl.java | 2 +- .../client/WithTransactionProseTest.java | 5 +- 4 files changed, 50 insertions(+), 54 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java index ed9bba51d7f..4f0d7749caa 100644 --- a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java +++ b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java @@ -24,46 +24,34 @@ import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; /** - * Implements exponential backoff with jitter for retry scenarios. + * Provides exponential backoff calculations with jitter for retry scenarios. */ -public enum ExponentialBackoff { - TRANSACTION(5.0, 500.0, 1.5); +public final class ExponentialBackoff { - private final double baseMs, maxMs, growth; + // Constants for transaction retry backoff + private static final double TRANSACTION_BASE_MS = 5.0; + private static final double TRANSACTION_MAX_MS = 500.0; + private static final double TRANSACTION_GROWTH = 1.5; - // TODO remove this global state once https://jira.mongodb.org/browse/JAVA-6060 is done + // TODO-JAVA-6079 private static DoubleSupplier testJitterSupplier = null; - ExponentialBackoff(final double baseMs, final double maxMs, final double growth) { - this.baseMs = baseMs; - this.maxMs = maxMs; - this.growth = growth; + private ExponentialBackoff() { } /** - * Calculate the next delay in milliseconds based on the retry count. + * Calculate the backoff in milliseconds for transaction retries. * - * @param retryCount The number of retries that have occurred. - * @return The calculated delay in milliseconds. + * @param attemptNumber The 0-based attempt number + * @return The calculated backoff in milliseconds. */ - public long calculateDelayBeforeNextRetryMs(final int retryCount) { + public static long calculateTransactionBackoffMs(final int attemptNumber) { double jitter = testJitterSupplier != null ? testJitterSupplier.getAsDouble() : ThreadLocalRandom.current().nextDouble(); - double backoff = Math.min(baseMs * Math.pow(growth, retryCount), maxMs); - return Math.round(jitter * backoff); - } - - /** - * Calculate the next delay in milliseconds based on the retry count and a provided jitter. - * - * @param retryCount The number of retries that have occurred. - * @param jitter A double in the range [0, 1) to apply as jitter. - * @return The calculated delay in milliseconds. - */ - public long calculateDelayBeforeNextRetryMs(final int retryCount, final double jitter) { - double backoff = Math.min(baseMs * Math.pow(growth, retryCount), maxMs); - return Math.round(jitter * backoff); + return Math.round(jitter * Math.min( + TRANSACTION_BASE_MS * Math.pow(TRANSACTION_GROWTH, attemptNumber), + TRANSACTION_MAX_MS)); } /** diff --git a/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java index 67238532488..0dbc5e48995 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java @@ -31,43 +31,50 @@ void testTransactionRetryBackoff() { // With jitter, actual values will be between 0 and these maxima double[] expectedMaxValues = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; - ExponentialBackoff backoff = ExponentialBackoff.TRANSACTION; - for (int retry = 0; retry < expectedMaxValues.length; retry++) { - long delay = backoff.calculateDelayBeforeNextRetryMs(retry); - assertTrue(delay >= 0 && delay <= Math.round(expectedMaxValues[retry]), String.format("Retry %d: delay should be 0-%d ms, got: %d", retry, Math.round(expectedMaxValues[retry]), delay)); + for (int attemptNumber = 0; attemptNumber < expectedMaxValues.length; attemptNumber++) { + long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); + assertTrue(backoff >= 0 && backoff <= Math.round(expectedMaxValues[attemptNumber]), + String.format("Attempt %d: backoff should be 0-%d ms, got: %d", attemptNumber, Math.round(expectedMaxValues[attemptNumber]), backoff)); } } @Test void testTransactionRetryBackoffRespectsMaximum() { - ExponentialBackoff backoff = ExponentialBackoff.TRANSACTION; - - // Even at high retry counts, delay should never exceed 500ms - for (int retry = 0; retry < 25; retry++) { - long delay = backoff.calculateDelayBeforeNextRetryMs(retry); - assertTrue(delay >= 0 && delay <= 500, String.format("Retry %d: delay should be capped at 500 ms, got: %d ms", retry, delay)); + // Even at high attempt numbers, backoff should never exceed 500ms + for (int attemptNumber = 0; attemptNumber < 25; attemptNumber++) { + long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); + assertTrue(backoff >= 0 && backoff <= 500, + String.format("Attempt %d: backoff should be capped at 500 ms, got: %d ms", attemptNumber, backoff)); } } @Test void testCustomJitter() { - ExponentialBackoff backoff = ExponentialBackoff.TRANSACTION; - - // Expected delays with jitter=1.0 and growth factor 1.5 - double[] expectedDelays = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; - double jitter = 1.0; + // Expected backoffs with jitter=1.0 and growth factor 1.5 + double[] expectedBackoffs = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; - for (int retry = 0; retry < expectedDelays.length; retry++) { - long delay = backoff.calculateDelayBeforeNextRetryMs(retry, jitter); - long expected = Math.round(expectedDelays[retry]); - assertEquals(expected, delay, String.format("Retry %d: with jitter=1.0, delay should be %d ms", retry, expected)); + // Test with jitter = 1.0 + ExponentialBackoff.setTestJitterSupplier(() -> 1.0); + try { + for (int attemptNumber = 0; attemptNumber < expectedBackoffs.length; attemptNumber++) { + long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); + long expected = Math.round(expectedBackoffs[attemptNumber]); + assertEquals(expected, backoff, + String.format("Attempt %d: with jitter=1.0, backoff should be %d ms", attemptNumber, expected)); + } + } finally { + ExponentialBackoff.clearTestJitterSupplier(); } - // With jitter = 0, all delays should be 0 - jitter = 0; - for (int retry = 0; retry < 10; retry++) { - long delay = backoff.calculateDelayBeforeNextRetryMs(retry, jitter); - assertEquals(0, delay, "With jitter=0, delay should always be 0 ms"); + // Test with jitter = 0, all backoffs should be 0 + ExponentialBackoff.setTestJitterSupplier(() -> 0.0); + try { + for (int attemptNumber = 0; attemptNumber < 10; attemptNumber++) { + long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); + assertEquals(0, backoff, "With jitter=0, backoff should always be 0 ms"); + } + } finally { + ExponentialBackoff.clearTestJitterSupplier(); } } } diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index 693df7dc7fa..6ab19c9811e 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -382,7 +382,7 @@ private TimeoutContext createTimeoutContext(final TransactionOptions transaction private static void backoff(final int transactionAttempt, final Timeout withTransactionTimeout, final MongoException lastError) { - long backoffMs = ExponentialBackoff.TRANSACTION.calculateDelayBeforeNextRetryMs(transactionAttempt - 1); + long backoffMs = ExponentialBackoff.calculateTransactionBackoffMs(transactionAttempt - 1); withTransactionTimeout.shortenBy(backoffMs, TimeUnit.MILLISECONDS).onExpired(() -> { throw lastError; }); diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index 514dbc4a2e6..a4bfdfa33f8 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -288,8 +288,9 @@ private static boolean canRunTests() { private static void doWithSystemNanoTimeHandle(final Consumer action) { long startNanos = SystemNanoTime.get(); - try (MockedStatic mockedStaticSystem = Mockito.mockStatic(SystemNanoTime.class)) { - action.accept(change -> mockedStaticSystem.when(SystemNanoTime::get).thenReturn(startNanos + change.toNanos())); + try (MockedStatic mockedStaticSystemNanoTime = Mockito.mockStatic(SystemNanoTime.class)) { + mockedStaticSystemNanoTime.when(SystemNanoTime::get).thenReturn(startNanos); + action.accept(change -> mockedStaticSystemNanoTime.when(SystemNanoTime::get).thenReturn(startNanos + change.toNanos())); } } From cae949ea0af11924ac63f1c7e619eb51c4366842 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 29 Jan 2026 01:07:39 +0000 Subject: [PATCH 48/60] Reduce flakiness in CSOT tests using withTransaction, by forcing an immediate retry without backoff. --- ...stractClientSideOperationsTimeoutProseTest.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 9ce58b1654f..668a46e0961 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -47,14 +47,11 @@ import com.mongodb.event.ConnectionClosedEvent; import com.mongodb.event.ConnectionCreatedEvent; import com.mongodb.event.ConnectionReadyEvent; - -import static com.mongodb.internal.connection.CommandHelper.HELLO; -import static com.mongodb.internal.connection.CommandHelper.LEGACY_HELLO; - import com.mongodb.internal.connection.InternalStreamConnection; import com.mongodb.internal.connection.ServerHelper; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.internal.connection.TestConnectionPoolListener; +import com.mongodb.internal.time.ExponentialBackoff; import com.mongodb.test.FlakyTest; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -93,6 +90,8 @@ import static com.mongodb.ClusterFixture.sleep; import static com.mongodb.client.Fixture.getDefaultDatabaseName; import static com.mongodb.client.Fixture.getPrimary; +import static com.mongodb.internal.connection.CommandHelper.HELLO; +import static com.mongodb.internal.connection.CommandHelper.LEGACY_HELLO; import static java.lang.Long.MAX_VALUE; import static java.lang.String.join; import static java.util.Arrays.asList; @@ -1106,6 +1105,11 @@ public void setUp() { filesCollectionHelper = new CollectionHelper<>(new BsonDocumentCodec(), gridFsFileNamespace); chunksCollectionHelper = new CollectionHelper<>(new BsonDocumentCodec(), gridFsChunksNamespace); commandListener = new TestCommandListener(); + + // setting jitter to 0 to make test using withTransaction deterministic (i.e retries immediately) otherwise we might get + // MongoCommandException setup in the failpoint instead of MongoOperationTimeoutException depending on the random jitter value. + ExponentialBackoff.setTestJitterSupplier(() -> 1.0); + } @AfterEach @@ -1128,6 +1132,8 @@ public void tearDown() throws InterruptedException { //noinspection ResultOfMethodCallIgnored executor.awaitTermination(MAX_VALUE, NANOSECONDS); } + + ExponentialBackoff.clearTestJitterSupplier(); } @AfterAll From 6a17834c6e060bcea81e5852dcbea7c67118ef89 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 29 Jan 2026 01:37:22 +0000 Subject: [PATCH 49/60] Using correct jitter value in CSOT tests using withTransaction --- .../client/AbstractClientSideOperationsTimeoutProseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 668a46e0961..13c0f6ed2e9 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -1108,7 +1108,7 @@ public void setUp() { // setting jitter to 0 to make test using withTransaction deterministic (i.e retries immediately) otherwise we might get // MongoCommandException setup in the failpoint instead of MongoOperationTimeoutException depending on the random jitter value. - ExponentialBackoff.setTestJitterSupplier(() -> 1.0); + ExponentialBackoff.setTestJitterSupplier(() -> 0); } From 98b8d28762dda91d131b49b5d9b25c11ff5c60eb Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 29 Jan 2026 14:19:25 +0000 Subject: [PATCH 50/60] Guarding setting a custom jitter for only sync tests --- ...bstractClientSideOperationsTimeoutProseTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 13c0f6ed2e9..4cd1e611c52 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -1106,10 +1106,11 @@ public void setUp() { chunksCollectionHelper = new CollectionHelper<>(new BsonDocumentCodec(), gridFsChunksNamespace); commandListener = new TestCommandListener(); - // setting jitter to 0 to make test using withTransaction deterministic (i.e retries immediately) otherwise we might get - // MongoCommandException setup in the failpoint instead of MongoOperationTimeoutException depending on the random jitter value. - ExponentialBackoff.setTestJitterSupplier(() -> 0); - + if (!isAsync()) { + // setting jitter to 0 to make test using withTransaction deterministic (i.e retries immediately) otherwise we might get + // MongoCommandException setup in the failpoint instead of MongoOperationTimeoutException depending on the random jitter value. + ExponentialBackoff.setTestJitterSupplier(() -> 0); + } } @AfterEach @@ -1133,7 +1134,9 @@ public void tearDown() throws InterruptedException { executor.awaitTermination(MAX_VALUE, NANOSECONDS); } - ExponentialBackoff.clearTestJitterSupplier(); + if (!isAsync()) { + ExponentialBackoff.clearTestJitterSupplier(); + } } @AfterAll From 556001201928f25b54f471eda11f7f79b150b38f Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 29 Jan 2026 18:37:52 +0000 Subject: [PATCH 51/60] Temp disable large encryption tests on mongocryptd (#1872) * Temp disable large encryption tests on mongocryptd JAVA-6077 --- .../ClientSideEncryptionBsonSizeLimitsSpecification.groovy | 1 + .../reactivestreams/client/ClientSideEncryptionCorpusTest.java | 1 + .../ClientSideEncryptionBsonSizeLimitsSpecification.groovy | 1 + .../com/mongodb/client/ClientSideEncryptionCorpusTest.java | 1 + 4 files changed, 4 insertions(+) diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionBsonSizeLimitsSpecification.groovy b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionBsonSizeLimitsSpecification.groovy index 874f2204c6d..d053202cada 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionBsonSizeLimitsSpecification.groovy +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionBsonSizeLimitsSpecification.groovy @@ -50,6 +50,7 @@ class ClientSideEncryptionBsonSizeLimitsSpecification extends FunctionalSpecific private MongoCollection autoEncryptingDataCollection def setup() { + assumeTrue("TODO JAVA-6077", false); assumeTrue('Key vault tests disabled', !System.getProperty('AWS_ACCESS_KEY_ID', '').isEmpty()) drop(keyVaultNamespace) diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionCorpusTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionCorpusTest.java index 1d98ede1ead..b24c695c317 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionCorpusTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideEncryptionCorpusTest.java @@ -72,6 +72,7 @@ public ClientSideEncryptionCorpusTest(final boolean useLocalSchema) { @Before public void setUp() throws IOException, URISyntaxException { + assumeTrue("TODO JAVA-6077", false); assumeTrue("Corpus tests disabled", hasEncryptionTestsEnabled()); MongoClientSettings clientSettings = getMongoClientBuilderFromConnectionString() diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionBsonSizeLimitsSpecification.groovy b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionBsonSizeLimitsSpecification.groovy index 68a5c1e9a1c..305df4dea83 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionBsonSizeLimitsSpecification.groovy +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionBsonSizeLimitsSpecification.groovy @@ -55,6 +55,7 @@ class ClientSideEncryptionBsonSizeLimitsSpecification extends FunctionalSpecific private MongoCollection autoEncryptingDataCollection def setup() { + assumeTrue("TODO JAVA-6077", false); assumeTrue(isClientSideEncryptionTest()) dataKeyCollection.drop() dataCollection.drop() diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionCorpusTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionCorpusTest.java index 3b4980e430d..dd90c57a419 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionCorpusTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideEncryptionCorpusTest.java @@ -71,6 +71,7 @@ public ClientSideEncryptionCorpusTest(final boolean useLocalSchema) { @Before public void setUp() throws IOException, URISyntaxException { + assumeTrue("TODO JAVA-6077", false); assumeTrue("Corpus tests disabled", hasEncryptionTestsEnabled()); MongoClientSettings clientSettings = getMongoClientSettingsBuilder() From ddfe918f327c802f233ce5a10099351ab8e4890f Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 2 Feb 2026 13:35:24 +0000 Subject: [PATCH 52/60] PR feedback --- .../internal/time/ExponentialBackoff.java | 4 ++-- .../{ => time}/ExponentialBackoffTest.java | 16 ++++++++-------- .../client/internal/ClientSessionImpl.java | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) rename driver-core/src/test/unit/com/mongodb/internal/{ => time}/ExponentialBackoffTest.java (87%) diff --git a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java index 4f0d7749caa..33d7148550d 100644 --- a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java +++ b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java @@ -42,7 +42,7 @@ private ExponentialBackoff() { /** * Calculate the backoff in milliseconds for transaction retries. * - * @param attemptNumber The 0-based attempt number + * @param attemptNumber The attempt number * @return The calculated backoff in milliseconds. */ public static long calculateTransactionBackoffMs(final int attemptNumber) { @@ -50,7 +50,7 @@ public static long calculateTransactionBackoffMs(final int attemptNumber) { ? testJitterSupplier.getAsDouble() : ThreadLocalRandom.current().nextDouble(); return Math.round(jitter * Math.min( - TRANSACTION_BASE_MS * Math.pow(TRANSACTION_GROWTH, attemptNumber), + TRANSACTION_BASE_MS * Math.pow(TRANSACTION_GROWTH, attemptNumber - 1), TRANSACTION_MAX_MS)); } diff --git a/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java similarity index 87% rename from driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java rename to driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java index 0dbc5e48995..68b88666ebd 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/ExponentialBackoffTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.mongodb.internal; +package com.mongodb.internal.time; -import com.mongodb.internal.time.ExponentialBackoff; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,17 +30,18 @@ void testTransactionRetryBackoff() { // With jitter, actual values will be between 0 and these maxima double[] expectedMaxValues = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; - for (int attemptNumber = 0; attemptNumber < expectedMaxValues.length; attemptNumber++) { + for (int attemptNumber = 1; attemptNumber <= expectedMaxValues.length; attemptNumber++) { long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); - assertTrue(backoff >= 0 && backoff <= Math.round(expectedMaxValues[attemptNumber]), - String.format("Attempt %d: backoff should be 0-%d ms, got: %d", attemptNumber, Math.round(expectedMaxValues[attemptNumber]), backoff)); + assertTrue(backoff >= 0 && backoff <= Math.round(expectedMaxValues[attemptNumber - 1]), + String.format("Attempt %d: backoff should be 0-%d ms, got: %d", attemptNumber, + Math.round(expectedMaxValues[attemptNumber - 1]), backoff)); } } @Test void testTransactionRetryBackoffRespectsMaximum() { // Even at high attempt numbers, backoff should never exceed 500ms - for (int attemptNumber = 0; attemptNumber < 25; attemptNumber++) { + for (int attemptNumber = 1; attemptNumber < 26; attemptNumber++) { long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); assertTrue(backoff >= 0 && backoff <= 500, String.format("Attempt %d: backoff should be capped at 500 ms, got: %d ms", attemptNumber, backoff)); @@ -56,9 +56,9 @@ void testCustomJitter() { // Test with jitter = 1.0 ExponentialBackoff.setTestJitterSupplier(() -> 1.0); try { - for (int attemptNumber = 0; attemptNumber < expectedBackoffs.length; attemptNumber++) { + for (int attemptNumber = 1; attemptNumber <= expectedBackoffs.length; attemptNumber++) { long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); - long expected = Math.round(expectedBackoffs[attemptNumber]); + long expected = Math.round(expectedBackoffs[attemptNumber - 1]); assertEquals(expected, backoff, String.format("Attempt %d: with jitter=1.0, backoff should be %d ms", attemptNumber, expected)); } diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index 6ab19c9811e..f7d5c770eaa 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -382,7 +382,7 @@ private TimeoutContext createTimeoutContext(final TransactionOptions transaction private static void backoff(final int transactionAttempt, final Timeout withTransactionTimeout, final MongoException lastError) { - long backoffMs = ExponentialBackoff.calculateTransactionBackoffMs(transactionAttempt - 1); + long backoffMs = ExponentialBackoff.calculateTransactionBackoffMs(transactionAttempt); withTransactionTimeout.shortenBy(backoffMs, TimeUnit.MILLISECONDS).onExpired(() -> { throw lastError; }); From c97073ec63b2e511e4624cedf819bd6ccde9ad23 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 2 Feb 2026 16:26:51 +0000 Subject: [PATCH 53/60] PR feedback --- .../main/com/mongodb/internal/time/ExponentialBackoff.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java index 33d7148550d..4b59b1f41d2 100644 --- a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java +++ b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java @@ -21,6 +21,7 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.function.DoubleSupplier; +import static com.mongodb.assertions.Assertions.assertTrue; import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; /** @@ -42,10 +43,11 @@ private ExponentialBackoff() { /** * Calculate the backoff in milliseconds for transaction retries. * - * @param attemptNumber The attempt number + * @param attemptNumber 0-based attempt number * @return The calculated backoff in milliseconds. */ public static long calculateTransactionBackoffMs(final int attemptNumber) { + assertTrue(attemptNumber > 0, "Attempt number must be greater than 0 in the context of transaction backoff calculation"); double jitter = testJitterSupplier != null ? testJitterSupplier.getAsDouble() : ThreadLocalRandom.current().nextDouble(); From f0973a1d537c91911d2be7c44b9960c1b2ddf9dd Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 2 Feb 2026 17:55:26 +0000 Subject: [PATCH 54/60] Fixing unit test --- .../unit/com/mongodb/internal/time/ExponentialBackoffTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java index 68b88666ebd..ba0f4804994 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java @@ -69,7 +69,7 @@ void testCustomJitter() { // Test with jitter = 0, all backoffs should be 0 ExponentialBackoff.setTestJitterSupplier(() -> 0.0); try { - for (int attemptNumber = 0; attemptNumber < 10; attemptNumber++) { + for (int attemptNumber = 1; attemptNumber < 11; attemptNumber++) { long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); assertEquals(0, backoff, "With jitter=0, backoff should always be 0 ms"); } From 1655dade0ca43118529ca78944ae4a7cc315507a Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 12 Feb 2026 15:21:15 +0000 Subject: [PATCH 55/60] Update driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java Co-authored-by: Valentin Kovalenko --- .../unit/com/mongodb/internal/time/ExponentialBackoffTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java index ba0f4804994..372009da421 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java @@ -21,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class ExponentialBackoffTest { +class ExponentialBackoffTest { @Test void testTransactionRetryBackoff() { From f5274cb6cc90e68b2ba2a3a529369097b92f0314 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 12 Feb 2026 15:25:20 +0000 Subject: [PATCH 56/60] Update driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java Co-authored-by: Valentin Kovalenko --- .../com/mongodb/internal/time/ExponentialBackoffTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java index 372009da421..6e8b4db0e52 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java @@ -32,9 +32,9 @@ void testTransactionRetryBackoff() { for (int attemptNumber = 1; attemptNumber <= expectedMaxValues.length; attemptNumber++) { long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); - assertTrue(backoff >= 0 && backoff <= Math.round(expectedMaxValues[attemptNumber - 1]), - String.format("Attempt %d: backoff should be 0-%d ms, got: %d", attemptNumber, - Math.round(expectedMaxValues[attemptNumber - 1]), backoff)); + long expectedBackoff = Math.round(expectedMaxValues[attemptNumber - 1]); + assertTrue(backoff >= 0 && backoff <= expectedBackoff, + String.format("Attempt %d: backoff should be between 0 ms and %d ms, got: %d", attemptNumber, expectedBackoff, backoff)); } } From 8105760dbf5344c72fc27bc8a97b41e3b41ab60f Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 12 Feb 2026 16:33:32 +0000 Subject: [PATCH 57/60] Update driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java Co-authored-by: Valentin Kovalenko --- .../unit/com/mongodb/internal/time/ExponentialBackoffTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java index 6e8b4db0e52..9961a32b149 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java @@ -69,7 +69,7 @@ void testCustomJitter() { // Test with jitter = 0, all backoffs should be 0 ExponentialBackoff.setTestJitterSupplier(() -> 0.0); try { - for (int attemptNumber = 1; attemptNumber < 11; attemptNumber++) { + for (int attemptNumber = 1; attemptNumber < expectedBackoffs.length; attemptNumber++) { long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); assertEquals(0, backoff, "With jitter=0, backoff should always be 0 ms"); } From 56fe91b432aa670772a29c6b00bfa3152a81c164 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 12 Feb 2026 16:37:50 +0000 Subject: [PATCH 58/60] Update driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java Co-authored-by: Valentin Kovalenko --- .../functional/com/mongodb/client/WithTransactionProseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index a4bfdfa33f8..daf7a57691f 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -214,7 +214,7 @@ public void testTimeoutMSAndLegacySettings() { /** * See - * Convenient API Prose Tests. + * Retry Backoff is Enforced. */ @DisplayName("Retry Backoff is Enforced") @Test From 7730fc684aa74be7bcae468c9aafdc76b3b089e2 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 12 Feb 2026 18:36:06 +0000 Subject: [PATCH 59/60] - PR feedback - Adding logic for DRIVERS-3391 --- .../internal/time/ExponentialBackoff.java | 9 ++-- .../internal/time/ExponentialBackoffTest.java | 31 +++++------ .../ClientSideOperationTimeoutProseTest.java | 3 ++ .../client/internal/ClientSessionImpl.java | 54 ++++++++++++------- ...tClientSideOperationsTimeoutProseTest.java | 10 ---- .../client/WithTransactionProseTest.java | 49 ++++++----------- 6 files changed, 77 insertions(+), 79 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java index 4b59b1f41d2..5f63d5c01c8 100644 --- a/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java +++ b/driver-core/src/main/com/mongodb/internal/time/ExponentialBackoff.java @@ -30,9 +30,12 @@ public final class ExponentialBackoff { // Constants for transaction retry backoff - private static final double TRANSACTION_BASE_MS = 5.0; - private static final double TRANSACTION_MAX_MS = 500.0; - private static final double TRANSACTION_GROWTH = 1.5; + @VisibleForTesting(otherwise = PRIVATE) + static final double TRANSACTION_BASE_MS = 5.0; + @VisibleForTesting(otherwise = PRIVATE) + static final double TRANSACTION_MAX_MS = 500.0; + @VisibleForTesting(otherwise = PRIVATE) + static final double TRANSACTION_GROWTH = 1.5; // TODO-JAVA-6079 private static DoubleSupplier testJitterSupplier = null; diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java index ba0f4804994..70967c9ba19 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/time/ExponentialBackoffTest.java @@ -22,43 +22,44 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class ExponentialBackoffTest { + // Expected backoffs with jitter=1.0 and growth factor ExponentialBackoff.TRANSACTION_GROWTH + private static final double[] EXPECTED_BACKOFFS_MAX_VALUES = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, + 192.21679688, 288.32519531, 432.48779297, 500.0}; @Test - void testTransactionRetryBackoff() { - // Test that the backoff sequence follows the expected pattern with growth factor 1.5 + void testCalculateTransactionBackoffMs() { + // Test that the backoff sequence follows the expected pattern with growth factor ExponentialBackoff.TRANSACTION_GROWTH // Expected sequence (without jitter): 5, 7.5, 11.25, ... // With jitter, actual values will be between 0 and these maxima - double[] expectedMaxValues = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; - for (int attemptNumber = 1; attemptNumber <= expectedMaxValues.length; attemptNumber++) { + for (int attemptNumber = 1; attemptNumber <= EXPECTED_BACKOFFS_MAX_VALUES.length; attemptNumber++) { long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); - assertTrue(backoff >= 0 && backoff <= Math.round(expectedMaxValues[attemptNumber - 1]), + long expectedBackoff = Math.round(EXPECTED_BACKOFFS_MAX_VALUES[attemptNumber - 1]); + assertTrue(backoff >= 0 && backoff <= expectedBackoff, String.format("Attempt %d: backoff should be 0-%d ms, got: %d", attemptNumber, - Math.round(expectedMaxValues[attemptNumber - 1]), backoff)); + expectedBackoff, backoff)); } } @Test - void testTransactionRetryBackoffRespectsMaximum() { - // Even at high attempt numbers, backoff should never exceed 500ms + void testCalculateTransactionBackoffMsRespectsMaximum() { + // Even at high attempt numbers, backoff should never exceed ExponentialBackoff.TRANSACTION_MAX_MS for (int attemptNumber = 1; attemptNumber < 26; attemptNumber++) { long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); - assertTrue(backoff >= 0 && backoff <= 500, - String.format("Attempt %d: backoff should be capped at 500 ms, got: %d ms", attemptNumber, backoff)); + assertTrue(backoff >= 0 && backoff <= ExponentialBackoff.TRANSACTION_MAX_MS, + String.format("Attempt %d: backoff should be capped at %f ms, got: %d ms", + attemptNumber, ExponentialBackoff.TRANSACTION_MAX_MS, backoff)); } } @Test void testCustomJitter() { - // Expected backoffs with jitter=1.0 and growth factor 1.5 - double[] expectedBackoffs = {5.0, 7.5, 11.25, 16.875, 25.3125, 37.96875, 56.953125, 85.4296875, 128.14453125, 192.21679688, 288.32519531, 432.48779297, 500.0}; - // Test with jitter = 1.0 ExponentialBackoff.setTestJitterSupplier(() -> 1.0); try { - for (int attemptNumber = 1; attemptNumber <= expectedBackoffs.length; attemptNumber++) { + for (int attemptNumber = 1; attemptNumber <= EXPECTED_BACKOFFS_MAX_VALUES.length; attemptNumber++) { long backoff = ExponentialBackoff.calculateTransactionBackoffMs(attemptNumber); - long expected = Math.round(expectedBackoffs[attemptNumber - 1]); + long expected = Math.round(EXPECTED_BACKOFFS_MAX_VALUES[attemptNumber - 1]); assertEquals(expected, backoff, String.format("Attempt %d: with jitter=1.0, backoff should be %d ms", attemptNumber, expected)); } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java index b922ec20b71..8e611d3d17b 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java @@ -28,6 +28,7 @@ import com.mongodb.client.model.changestream.FullDocument; import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandStartedEvent; +import com.mongodb.internal.time.ExponentialBackoff; import com.mongodb.reactivestreams.client.gridfs.GridFSBucket; import com.mongodb.reactivestreams.client.gridfs.GridFSBuckets; import com.mongodb.reactivestreams.client.syncadapter.SyncGridFSBucket; @@ -501,6 +502,7 @@ private void assertOnlyOneCommandTimeoutFailure(final String command) { public void setUp() { super.setUp(); SyncMongoClient.enableSleepAfterSessionClose(postSessionCloseSleep()); + ExponentialBackoff.setTestJitterSupplier(() -> 0); } @Override @@ -508,6 +510,7 @@ public void setUp() { public void tearDown() throws InterruptedException { super.tearDown(); SyncMongoClient.disableSleep(); + ExponentialBackoff.clearTestJitterSupplier(); } @Override diff --git a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java index f7d5c770eaa..18f782728ac 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/ClientSessionImpl.java @@ -22,6 +22,7 @@ import com.mongodb.MongoExecutionTimeoutException; import com.mongodb.MongoInternalException; import com.mongodb.MongoOperationTimeoutException; +import com.mongodb.MongoTimeoutException; import com.mongodb.ReadConcern; import com.mongodb.TransactionOptions; import com.mongodb.WriteConcern; @@ -51,6 +52,7 @@ import static com.mongodb.assertions.Assertions.assertTrue; import static com.mongodb.assertions.Assertions.isTrue; import static com.mongodb.assertions.Assertions.notNull; +import static com.mongodb.internal.TimeoutContext.createMongoTimeoutException; import static com.mongodb.internal.thread.InterruptionUtil.interruptAndCreateMongoInterruptedException; final class ClientSessionImpl extends BaseClientSessionImpl implements ClientSession { @@ -256,6 +258,7 @@ public T withTransaction(final TransactionBody transactionBody) { public T withTransaction(final TransactionBody transactionBody, final TransactionOptions options) { notNull("transactionBody", transactionBody); TimeoutContext withTransactionTimeoutContext = createTimeoutContext(options); + final boolean hasTimeoutMS = withTransactionTimeoutContext.hasTimeoutMS(); Timeout withTransactionTimeout = withTransactionTimeoutContext.timeoutOrAlternative( assertNotNull(TimeoutContext.startTimeout(MAX_RETRY_TIME_LIMIT_MS))); BooleanSupplier withTransactionTimeoutExpired = () -> withTransactionTimeout.call(TimeUnit.MILLISECONDS, @@ -267,7 +270,7 @@ public T withTransaction(final TransactionBody transactionBody, final Tra outer: while (true) { if (transactionAttempt > 0) { - backoff(transactionAttempt, withTransactionTimeout, assertNotNull(lastError)); + backoff(transactionAttempt, withTransactionTimeout, assertNotNull(lastError), hasTimeoutMS); } T retVal; try { @@ -286,12 +289,15 @@ public T withTransaction(final TransactionBody transactionBody, final Tra lastError = (MongoException) e; if (!(e instanceof MongoOperationTimeoutException)) { MongoException exceptionToHandle = OperationHelper.unwrap((MongoException) e); - if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL) - && !withTransactionTimeoutExpired.getAsBoolean()) { - if (transactionSpan != null) { - transactionSpan.spanFinalizing(false); + if (exceptionToHandle.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)) { + if (withTransactionTimeoutExpired.getAsBoolean()) { + throw timeoutException(hasTimeoutMS, e); + } else { + if (transactionSpan != null) { + transactionSpan.spanFinalizing(false); + } + continue; } - continue; } } } @@ -305,18 +311,22 @@ public T withTransaction(final TransactionBody transactionBody, final Tra } catch (MongoException e) { lastError = e; clearTransactionContextOnError(e); - if (!(e instanceof MongoOperationTimeoutException) - && !withTransactionTimeoutExpired.getAsBoolean()) { - applyMajorityWriteConcernToTransactionOptions(); - - if (!(e instanceof MongoExecutionTimeoutException) - && e.hasErrorLabel(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) { - continue; - } else if (e.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)) { - if (transactionSpan != null) { - transactionSpan.spanFinalizing(true); + if (!(e instanceof MongoOperationTimeoutException)) { + if (withTransactionTimeoutExpired.getAsBoolean()) { + throw timeoutException(hasTimeoutMS, e); + + } else { + applyMajorityWriteConcernToTransactionOptions(); + + if (!(e instanceof MongoExecutionTimeoutException) + && e.hasErrorLabel(UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) { + continue; + } else if (e.hasErrorLabel(TRANSIENT_TRANSACTION_ERROR_LABEL)) { + if (transactionSpan != null) { + transactionSpan.spanFinalizing(true); + } + continue outer; } - continue outer; } } throw e; @@ -381,10 +391,10 @@ private TimeoutContext createTimeoutContext(final TransactionOptions transaction } private static void backoff(final int transactionAttempt, - final Timeout withTransactionTimeout, final MongoException lastError) { + final Timeout withTransactionTimeout, final MongoException lastError, final boolean hasTimeoutMS) { long backoffMs = ExponentialBackoff.calculateTransactionBackoffMs(transactionAttempt); withTransactionTimeout.shortenBy(backoffMs, TimeUnit.MILLISECONDS).onExpired(() -> { - throw lastError; + throw timeoutException(hasTimeoutMS, lastError); }); try { if (backoffMs > 0) { @@ -394,4 +404,10 @@ private static void backoff(final int transactionAttempt, throw interruptAndCreateMongoInterruptedException("Transaction retry interrupted", e); } } + + private static MongoException timeoutException(final boolean hasTimeoutMS, final Throwable cause) { + return hasTimeoutMS + ? createMongoTimeoutException(cause) // CSOT timeout exception + : new MongoTimeoutException("Operation exceeded the timeout limit", cause); // Legacy timeout exception + } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 4cd1e611c52..d4908dc4b49 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -51,7 +51,6 @@ import com.mongodb.internal.connection.ServerHelper; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.internal.connection.TestConnectionPoolListener; -import com.mongodb.internal.time.ExponentialBackoff; import com.mongodb.test.FlakyTest; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -1106,11 +1105,6 @@ public void setUp() { chunksCollectionHelper = new CollectionHelper<>(new BsonDocumentCodec(), gridFsChunksNamespace); commandListener = new TestCommandListener(); - if (!isAsync()) { - // setting jitter to 0 to make test using withTransaction deterministic (i.e retries immediately) otherwise we might get - // MongoCommandException setup in the failpoint instead of MongoOperationTimeoutException depending on the random jitter value. - ExponentialBackoff.setTestJitterSupplier(() -> 0); - } } @AfterEach @@ -1133,10 +1127,6 @@ public void tearDown() throws InterruptedException { //noinspection ResultOfMethodCallIgnored executor.awaitTermination(MAX_VALUE, NANOSECONDS); } - - if (!isAsync()) { - ExponentialBackoff.clearTestJitterSupplier(); - } } @AfterAll diff --git a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java index a4bfdfa33f8..3f636bc57e8 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/WithTransactionProseTest.java @@ -219,44 +219,17 @@ public void testTimeoutMSAndLegacySettings() { @DisplayName("Retry Backoff is Enforced") @Test public void testRetryBackoffIsEnforced() throws InterruptedException { - // Run with jitter = 0 (no backoff) - ExponentialBackoff.setTestJitterSupplier(() -> 0.0); - - BsonDocument failPointDocument = BsonDocument.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " - + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251}}"); - - long noBackoffTime; - try (ClientSession session = client.startSession(); - FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { - StartTime startTime = StartTime.now(); - session.withTransaction(() -> collection.insertOne(session, Document.parse("{}"))); - noBackoffTime = startTime.elapsed().toMillis(); - } finally { - // Clear the test jitter supplier to avoid affecting other tests - ExponentialBackoff.clearTestJitterSupplier(); - } - - // Run with jitter = 1 (full backoff) - ExponentialBackoff.setTestJitterSupplier(() -> 1.0); - - failPointDocument = BsonDocument.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " + final BsonDocument failPointDocument = BsonDocument.parse("{'configureFailPoint': 'failCommand', 'mode': {'times': 13}, " + "'data': {'failCommands': ['commitTransaction'], 'errorCode': 251}}"); - long withBackoffTime; - try (ClientSession session = client.startSession(); - FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { - StartTime startTime = StartTime.now(); - session.withTransaction(() -> collection.insertOne(session, Document.parse("{}"))); - withBackoffTime = startTime.elapsed().toMillis(); - } finally { - ExponentialBackoff.clearTestJitterSupplier(); - } + long noBackoffTime = measureTransactionLatency(0.0, failPointDocument); + long withBackoffTime = measureTransactionLatency(1.0, failPointDocument); long expectedWithBackoffTime = noBackoffTime + 1800; long actualDifference = Math.abs(withBackoffTime - expectedWithBackoffTime); - assertTrue(actualDifference < 1000, String.format("Expected withBackoffTime to be ~% dms (noBackoffTime %d ms + 1800 ms), but" - + " got %d ms. Difference: %d ms (tolerance: 1000 ms per spec)", expectedWithBackoffTime, noBackoffTime, withBackoffTime, + assertTrue(actualDifference < 500, String.format("Expected withBackoffTime to be ~% dms (noBackoffTime %d ms + 1800 ms), but" + + " got %d ms. Difference: %d ms (tolerance: 500 ms per spec)", expectedWithBackoffTime, noBackoffTime, withBackoffTime, actualDifference)); } @@ -282,6 +255,18 @@ public void testExponentialBackoffOnTransientError() throws InterruptedException } } + private long measureTransactionLatency(final double jitter, final BsonDocument failPointDocument) throws InterruptedException { + ExponentialBackoff.setTestJitterSupplier(() -> jitter); + try (ClientSession session = client.startSession(); + FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { + StartTime startTime = StartTime.now(); + session.withTransaction(() -> collection.insertOne(session, Document.parse("{}"))); + return startTime.elapsed().toMillis(); + } finally { + ExponentialBackoff.clearTestJitterSupplier(); + } + } + private static boolean canRunTests() { return isSharded() || isDiscoverableReplicaSet(); } From a4d90d0b14ab9906cd8f0eef999840880cd1e145 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 12 Feb 2026 19:27:54 +0000 Subject: [PATCH 60/60] moved logic of ExponentialBackoff cleanup to correct class --- .../ClientSideOperationTimeoutProseTest.java | 3 --- .../ClientSideOperationTimeoutProseTest.java | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java index 8e611d3d17b..b922ec20b71 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java @@ -28,7 +28,6 @@ import com.mongodb.client.model.changestream.FullDocument; import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandStartedEvent; -import com.mongodb.internal.time.ExponentialBackoff; import com.mongodb.reactivestreams.client.gridfs.GridFSBucket; import com.mongodb.reactivestreams.client.gridfs.GridFSBuckets; import com.mongodb.reactivestreams.client.syncadapter.SyncGridFSBucket; @@ -502,7 +501,6 @@ private void assertOnlyOneCommandTimeoutFailure(final String command) { public void setUp() { super.setUp(); SyncMongoClient.enableSleepAfterSessionClose(postSessionCloseSleep()); - ExponentialBackoff.setTestJitterSupplier(() -> 0); } @Override @@ -510,7 +508,6 @@ public void setUp() { public void tearDown() throws InterruptedException { super.tearDown(); SyncMongoClient.disableSleep(); - ExponentialBackoff.clearTestJitterSupplier(); } @Override diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java index 4dcbc4d1a0f..978fece912b 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java @@ -19,6 +19,7 @@ import com.mongodb.MongoClientSettings; import com.mongodb.client.gridfs.GridFSBucket; import com.mongodb.client.gridfs.GridFSBuckets; +import com.mongodb.internal.time.ExponentialBackoff; /** @@ -36,6 +37,22 @@ protected GridFSBucket createGridFsBucket(final MongoDatabase mongoDatabase, fin return GridFSBuckets.create(mongoDatabase, bucketName); } + @Override + public void setUp() { + super.setUp(); + ExponentialBackoff.setTestJitterSupplier(() -> 0); + } + + @Override + public void tearDown() throws InterruptedException { + super.tearDown(); + try { + super.tearDown(); + } finally { + ExponentialBackoff.clearTestJitterSupplier(); + } + } + @Override protected boolean isAsync() { return false;