diff --git a/source/open-telemetry/open-telemetry.md b/source/open-telemetry/open-telemetry.md index 3027900988..b41d2ed3e6 100644 --- a/source/open-telemetry/open-telemetry.md +++ b/source/open-telemetry/open-telemetry.md @@ -135,6 +135,35 @@ When a user commits or aborts a transaction with `commitTransaction` or `abortTr In case of `withTransaction` operation spans for operations that are executed inside the callbacks SHOULD be nested into the `withTransaction` span. +##### Cursor Iteration (`getMore`) + +If the driver does not expose the cursor to the caller, but iterates it internally to produce the return value of one +public API call (e.g., a `find` helper returning an array of all matching documents), the driver MUST NOT create +additional operation spans. Every `getMore` command span for that call MUST be nested under the call's single operation +span. + +If the driver returns the cursor to the caller and the caller drives iteration, the driver MUST create a new operation +span for each `getMore` it sends. The span MUST be created within the current span of the host application and MUST be +named according to [Operation Span Name](#operation-span-name): `getMore db.collection_name` for a cursor over a +collection, or `getMore db` when the cursor targets no specific collection (e.g., a cursor from `listCollections`), per +[db.collection.name](#dbcollectionname). The `getMore` command span MUST be nested under it, per +[Instrumenting Server Commands](#instrumenting-server-commands). + +This operation span MUST NOT be nested under the operation span of the command that created the cursor. A host +application may do unrelated work between batches, and nesting each `getMore` under the cursor-creating operation would +attribute that work to the original operation. Ordinary nesting still applies otherwise. A cursor iterated inside a +`withTransaction` callback nests into the `withTransaction` span, and a cursor iterated inside a transaction started +with the core transaction API nests into the pseudo operation `transaction` span. + +Each `getMore` operation span MUST be finished once its command completes. No span is scoped to a cursor's lifetime, so +a cursor that is never exhausted (e.g., a tailable cursor) leaves nothing unfinished. + +A `getMore` is not retryable, but a change stream may resume after one fails. A resume MUST NOT extend the failed +`getMore` operation span: drivers MUST finish that span with its error, and the `aggregate` and `getMore` commands that +re-establish the cursor MUST each be nested under new operation spans. Drivers MUST NOT create an operation span for the +`killCursors` a resume sends; it is internal cleanup rather than a public API call, so its command span is created +within whatever span is current. + ##### Operation Span Name The span name SHOULD be: @@ -163,6 +192,7 @@ Spans SHOULD have the following attributes: | `db.collection.name` | `string` | The collection being accessed within the database stated in `db.namespace` | Required if available | | `db.operation.name` | `string` | The name of the driver operation being executed | Required | | `db.operation.summary` | `string` | Equivalent to span name | Required | +| `db.mongodb.cursor_id` | `int64` | If a cursor is created or used in the operation (see below) | Conditional | Not all attributes are available at the moment of span creation. Drivers need to add attributes at later stages, which requires an operation span to be available throughout the complete operation lifecycle. @@ -198,6 +228,16 @@ Examples: - `abortTransaction` → *omitted* - client `bulkWrite` → *omitted* + + +###### db.mongodb.cursor_id + +If the operation creates a cursor, or operates on a single existing cursor, the `cursor_id` attribute MUST be added to +the operation span, following the same rules as the command span attribute of the same name, including its omissions: +never a literal `0`, and never for a command that may operate on several cursors at once, such as `killCursors` (see +[db.mongodb.cursor_id](#command-cursor-id)). When the driver iterates a cursor internally, the value is the id of the +cursor the operation created; for a caller-driven `getMore`, it is the id the driver sent. + ##### Exceptions If the driver operation fails with an exception, drivers MUST record an exception to the current operation span. This @@ -246,7 +286,7 @@ Spans SHOULD have the following attributes: | `db.mongodb.server_connection_id` | `int64` | Server connection id | Required if available | | `db.mongodb.driver_connection_id` | `int64` | Local connection id | Required if available | | `db.query.text` | `string` | Database command that was sent to the server. Content should be equivalent to the `document` field of the CommandStartedEvent of the command monitoring. | Conditional | -| `db.mongodb.cursor_id` | `int64` | If a cursor is created or used in the operation | Required if available | +| `db.mongodb.cursor_id` | `int64` | If a cursor is created or used in the command (see below) | Conditional | | `db.mongodb.lsid` | `string` | Logical session id | Required if available | | `db.mongodb.txn_number` | `int64` | Transaction number | Required if available | @@ -308,9 +348,22 @@ added and truncated to the provided value (similar to the Logging specification) On the `MongoClient` level this configuration can be implemented with a `MongoClient` option, for example, `tracing.query_text_max_length`. + + ###### db.mongodb.cursor_id -If the command returns a cursor, or uses a cursor, the `cursor_id` attribute SHOULD be added. +If the command creates a cursor (e.g., `find`, `aggregate`, `listIndexes`) and the server returns a non-zero cursor id, +the `cursor_id` attribute MUST be added. + +If the command operates on a single existing cursor (e.g., `getMore`), the attribute MUST be added, holding the cursor +id the driver sent. It MUST still be added when the reply returns a cursor id of `0` to signal the cursor is now +exhausted; the id the command operated on is the one worth recording. + +If a command may operate on several cursors at once (e.g., `killCursors`, whose `cursors` field is an array), the +attribute MUST be omitted: it is a single `int64` and has no defined value for such a command. + +A cursor id of `0` means no server-side cursor remains. Drivers MUST NOT add the attribute with a value of `0`, and MUST +omit it when a cursor-creating command's reply returns `0`. ##### Exceptions @@ -357,6 +410,7 @@ The OpenTelemetry specification covers all driver operations including but not l | `dropCollection` | [tests/operation/drop_collection.yml](tests/operation/drop_collection.yml) | | `dropIndexes` | [tests/operation/drop_indexes.yml](tests/operation/drop_indexes.yml) | | `find` | [tests/operation/find.yml](tests/operation/find.yml) | +| `getMore` | [tests/operation/get_more.yml](tests/operation/get_more.yml) | | `listCollections` | [tests/operation/list_collections.yml](tests/operation/list_collections.yml) | | `listDatabases` | [tests/operation/list_databases.yml](tests/operation/list_databases.yml) | | `listIndexes` | [tests/operation/list_indexes.yml](tests/operation/list_indexes.yml) | @@ -425,6 +479,11 @@ A URI options can be added later if we realise our users need it, while the oppo ## Changelog +- 2026-08-11: Specified that each `getMore` command is nested under its own new operation span, sibling to the operation + span of the command that created the cursor, when the caller drives cursor iteration. Specified that + `db.mongodb.cursor_id` MUST be added to operation spans and to command spans that create a cursor with a non-zero id + or that operate on a single existing cursor, and MUST be omitted rather than set to `0` when no server-side cursor + remains. - 2026-07-31: Allowed the `update` test to accept `multi` and `upsert` at their default values, and added `initialData` to the operation tests that create or modify collections. - 2026-06-16: Clarified that the `db.query.text` attribute should be serialized to Relaxed Extended JSON. diff --git a/source/open-telemetry/tests/README.md b/source/open-telemetry/tests/README.md index 99bcc8d293..b230e87ae1 100644 --- a/source/open-telemetry/tests/README.md +++ b/source/open-telemetry/tests/README.md @@ -58,3 +58,20 @@ expectTracingMessages: 7. Create a new `MongoClient`. 8. Perform the same database operation. 9. Assert that the emitted tracing span does not include the `db.query.text` attribute. + +*Test 3: `getMore` inside a `withTransaction` callback nests under the transaction span* + +This test covers the convenient transaction API. The core transaction API case is covered by the unified test +[tests/transaction/get_more.yml](transaction/get_more.yml). + +This test requires a replica set or a sharded cluster running server version 4.4 or later, matching the existing +convenient transaction API fixture [tests/transaction/convenient.yml](transaction/convenient.yml). + +1. Create a `MongoClient` with tracing enabled. +2. Insert three documents into a test collection. +3. Start a session and call `withTransaction`. In the callback, create a cursor over that collection with `find` and a + `batchSize` of `2`, then iterate the cursor until it is exhausted. This sends exactly one `getMore` inside the + transaction. +4. Assert that a `transaction` span was emitted, and that both the `find` operation span and the `getMore` operation + span are nested directly under it. +5. Assert that the `getMore` operation span is a sibling of the `find` operation span, and is not nested under it. diff --git a/source/open-telemetry/tests/operation/get_more.json b/source/open-telemetry/tests/operation/get_more.json new file mode 100644 index 0000000000..e14499fb29 --- /dev/null +++ b/source/open-telemetry/tests/operation/get_more.json @@ -0,0 +1,306 @@ +{ + "description": "operation getMore", + "schemaVersion": "1.29", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": {} + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-get-more" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-get-more", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "tests": [ + { + "description": "getMore is nested under its own operation span", + "operations": [ + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 1 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 2 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 3 + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "find operation-get-more.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-get-more", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-get-more.test", + "db.mongodb.cursor_id": { + "$$gte": 1 + } + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-get-more", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "find operation-get-more.test", + "db.mongodb.cursor_id": { + "$$gte": 1 + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + }, + { + "name": "getMore operation-get-more.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-get-more", + "db.collection.name": "test", + "db.operation.name": "getMore", + "db.operation.summary": "getMore operation-get-more.test", + "db.mongodb.cursor_id": { + "$$gte": 1 + } + }, + "nested": [ + { + "name": "getMore", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-get-more", + "db.collection.name": "test", + "db.command.name": "getMore", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "getMore operation-get-more.test", + "db.mongodb.cursor_id": { + "$$gte": 1 + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + }, + { + "description": "cursor_id is omitted when the server returns cursor id 0", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "find operation-get-more.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-get-more", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-get-more.test", + "db.mongodb.cursor_id": { + "$$exists": false + } + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-get-more", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "find operation-get-more.test", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/source/open-telemetry/tests/operation/get_more.yml b/source/open-telemetry/tests/operation/get_more.yml new file mode 100644 index 0000000000..7890513d0c --- /dev/null +++ b/source/open-telemetry/tests/operation/get_more.yml @@ -0,0 +1,162 @@ +description: operation getMore +schemaVersion: '1.29' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: {} + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name operation-get-more + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } +tests: + - description: getMore is nested under its own operation span + operations: + - name: createFindCursor + object: *collection0 + arguments: + filter: {} + batchSize: 2 + saveResultAsEntity: &cursor0 cursor0 + - name: iterateUntilDocumentOrError + object: *cursor0 + expectResult: { _id: 1 } + - name: iterateUntilDocumentOrError + object: *cursor0 + expectResult: { _id: 2 } + - name: iterateUntilDocumentOrError + object: *cursor0 + expectResult: { _id: 3 } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: find operation-get-more.test + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.operation.name: find + db.operation.summary: find operation-get-more.test + # This find created a cursor with a non-zero id, since batchSize 2 leaves it open, + # so cursor_id is required on the operation span as well as the command span. + db.mongodb.cursor_id: { $$gte: 1 } + # Negative assertion: this `nested` array lists the `find` command span and nothing + # else. A driver that nests the getMore under the cursor-creating operation produces a + # second entry here, which `ignoreExtraSpans: false` rejects, because the unified test + # format spec states that ignoreExtraSpans applies at every level of the span tree. + nested: + - name: find + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.command.name: find + network.transport: tcp + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + db.query.summary: find operation-get-more.test + db.mongodb.cursor_id: { $$gte: 1 } + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] + - name: getMore operation-get-more.test + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.operation.name: getMore + db.operation.summary: getMore operation-get-more.test + # This operation operates on an existing cursor, so cursor_id is required here too. + db.mongodb.cursor_id: { $$gte: 1 } + nested: + - name: getMore + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.command.name: getMore + network.transport: tcp + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + db.query.summary: getMore operation-get-more.test + # This getMore drains the cursor, so the server's reply carries cursor id 0. + # Asserting a lower bound of 1 is what pins the requirement: a driver that + # recorded the reply's 0 instead of the id it sent would fail here. + db.mongodb.cursor_id: { $$gte: 1 } + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] + + - description: cursor_id is omitted when the server returns cursor id 0 + operations: + # No batchSize: all three documents come back in the first batch, the server replies with + # cursor id 0, and no cursor is left open. The UTF `find` operation fully iterates, so a + # driver that still issued a getMore here would also be caught by ignoreExtraSpans: false. + - name: find + object: *collection0 + arguments: + filter: {} + expectResult: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: find operation-get-more.test + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.operation.name: find + db.operation.summary: find operation-get-more.test + # This find returned everything in the first batch, so no cursor was left open and + # cursor_id must be omitted from the operation span as well as the command span. + db.mongodb.cursor_id: { $$exists: false } + nested: + - name: find + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.command.name: find + network.transport: tcp + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + db.query.summary: find operation-get-more.test + # Cursor id 0 means no server-side cursor remains: the attribute must be + # omitted, not emitted as 0. + db.mongodb.cursor_id: { $$exists: false } + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/source/open-telemetry/tests/transaction/get_more.json b/source/open-telemetry/tests/transaction/get_more.json new file mode 100644 index 0000000000..eaccd34431 --- /dev/null +++ b/source/open-telemetry/tests/transaction/get_more.json @@ -0,0 +1,209 @@ +{ + "description": "transaction getMore spans", + "schemaVersion": "1.29", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": {} + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-get-more" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-get-more", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "tests": [ + { + "description": "getMore inside a transaction nests under the transaction span", + "operations": [ + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2, + "session": "session0" + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 1 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 2 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 3 + } + }, + { + "name": "commitTransaction", + "object": "session0" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "transaction", + "attributes": { + "db.system.name": "mongodb" + }, + "nested": [ + { + "name": "find transaction-get-more.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-get-more", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find transaction-get-more.test", + "db.mongodb.cursor_id": { + "$$gte": 1 + } + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-get-more", + "db.collection.name": "test", + "db.command.name": "find", + "db.query.summary": "find transaction-get-more.test", + "db.mongodb.cursor_id": { + "$$gte": 1 + } + } + } + ] + }, + { + "name": "getMore transaction-get-more.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-get-more", + "db.collection.name": "test", + "db.operation.name": "getMore", + "db.operation.summary": "getMore transaction-get-more.test", + "db.mongodb.cursor_id": { + "$$gte": 1 + } + }, + "nested": [ + { + "name": "getMore", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-get-more", + "db.collection.name": "test", + "db.command.name": "getMore", + "db.query.summary": "getMore transaction-get-more.test", + "db.mongodb.cursor_id": { + "$$gte": 1 + } + } + } + ] + }, + { + "name": "commitTransaction admin", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": "commitTransaction", + "db.operation.summary": "commitTransaction admin" + }, + "nested": [ + { + "name": "commitTransaction", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.command.name": "commitTransaction", + "db.query.summary": "commitTransaction admin" + } + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/source/open-telemetry/tests/transaction/get_more.yml b/source/open-telemetry/tests/transaction/get_more.yml new file mode 100644 index 0000000000..c172e58ae1 --- /dev/null +++ b/source/open-telemetry/tests/transaction/get_more.yml @@ -0,0 +1,119 @@ +description: transaction getMore spans +schemaVersion: '1.29' +runOnRequirements: + - minServerVersion: '4.0' + topologies: + - replicaset + - minServerVersion: '4.1.8' + topologies: + - sharded + - load-balanced +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: {} + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name transaction-get-more + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + - session: + id: &session0 session0 + client: *client0 +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } +tests: + - description: getMore inside a transaction nests under the transaction span + operations: + - name: startTransaction + object: *session0 + # Three documents with a batchSize of two sends exactly one getMore, which + # exhausts the cursor inside the transaction. + - name: createFindCursor + object: *collection0 + arguments: + filter: {} + batchSize: 2 + session: *session0 + saveResultAsEntity: &cursor0 cursor0 + - name: iterateUntilDocumentOrError + object: *cursor0 + expectResult: { _id: 1 } + - name: iterateUntilDocumentOrError + object: *cursor0 + expectResult: { _id: 2 } + - name: iterateUntilDocumentOrError + object: *cursor0 + expectResult: { _id: 3 } + - name: commitTransaction + object: *session0 + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: transaction + attributes: + db.system.name: mongodb + # The getMore operation span is a sibling of the find operation span + # that created the cursor, not nested under it, even though both + # belong to the transaction. + nested: + - name: find transaction-get-more.test + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.operation.name: find + db.operation.summary: find transaction-get-more.test + db.mongodb.cursor_id: { $$gte: 1 } + nested: + - name: find + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.command.name: find + db.query.summary: find transaction-get-more.test + db.mongodb.cursor_id: { $$gte: 1 } + - name: getMore transaction-get-more.test + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.operation.name: getMore + db.operation.summary: getMore transaction-get-more.test + db.mongodb.cursor_id: { $$gte: 1 } + nested: + - name: getMore + attributes: + db.system.name: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.command.name: getMore + db.query.summary: getMore transaction-get-more.test + db.mongodb.cursor_id: { $$gte: 1 } + - name: commitTransaction admin + attributes: + db.system.name: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.operation.name: commitTransaction + db.operation.summary: commitTransaction admin + nested: + - name: commitTransaction + attributes: + db.system.name: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.command.name: commitTransaction + db.query.summary: commitTransaction admin diff --git a/source/unified-test-format/schema-1.29.json b/source/unified-test-format/schema-1.29.json new file mode 100644 index 0000000000..1801f4cff1 --- /dev/null +++ b/source/unified-test-format/schema-1.29.json @@ -0,0 +1,1274 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema#", + "title": "Unified Test Format", + "type": "object", + "additionalProperties": false, + "required": [ + "description", + "schemaVersion", + "tests" + ], + "properties": { + "description": { + "type": "string" + }, + "schemaVersion": { + "$ref": "#/definitions/version" + }, + "runOnRequirements": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/runOnRequirement" + } + }, + "createEntities": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/entity" + } + }, + "initialData": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/collectionData" + } + }, + "tests": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/test" + } + }, + "_yamlAnchors": { + "type": "object", + "additionalProperties": true + } + }, + "definitions": { + "version": { + "type": "string", + "pattern": "^[0-9]+(\\.[0-9]+){1,2}$" + }, + "runOnRequirement": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "maxServerVersion": { + "$ref": "#/definitions/version" + }, + "minServerVersion": { + "$ref": "#/definitions/version" + }, + "topologies": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "single", + "replicaset", + "sharded", + "sharded-replicaset", + "load-balanced" + ] + } + }, + "serverless": { + "type": "string", + "enum": [ + "require", + "forbid", + "allow" + ] + }, + "serverParameters": { + "type": "object", + "minProperties": 1 + }, + "auth": { + "type": "boolean" + }, + "authMechanism": { + "type": "string" + }, + "csfle": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "minLibmongocryptVersion": { + "$ref": "#/definitions/version" + } + } + } + ] + } + } + }, + "entity": { + "type": "object", + "additionalProperties": false, + "maxProperties": 1, + "minProperties": 1, + "properties": { + "client": { + "type": "object", + "additionalProperties": false, + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "uriOptions": { + "type": "object" + }, + "useMultipleMongoses": { + "type": "boolean" + }, + "observeEvents": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent", + "poolCreatedEvent", + "poolReadyEvent", + "poolClearedEvent", + "poolClosedEvent", + "connectionCreatedEvent", + "connectionReadyEvent", + "connectionClosedEvent", + "connectionCheckOutStartedEvent", + "connectionCheckOutFailedEvent", + "connectionCheckedOutEvent", + "connectionCheckedInEvent", + "serverDescriptionChangedEvent", + "topologyDescriptionChangedEvent", + "topologyOpeningEvent", + "topologyClosedEvent" + ] + } + }, + "ignoreCommandMonitoringEvents": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "storeEventsAsEntities": { + "deprecated": true, + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/storeEventsAsEntity" + } + }, + "observeLogMessages": { + "type": "object", + "minProperties": 1, + "additionalProperties": false, + "properties": { + "command": { + "$ref": "#/definitions/logSeverityLevel" + }, + "topology": { + "$ref": "#/definitions/logSeverityLevel" + }, + "serverSelection": { + "$ref": "#/definitions/logSeverityLevel" + }, + "connection": { + "$ref": "#/definitions/logSeverityLevel" + } + } + }, + "observeTracingMessages": { + "type": "object", + "additionalProperties": false, + "properties": { + "enableCommandPayload": { + "type": "boolean" + } + } + }, + "serverApi": { + "$ref": "#/definitions/serverApi" + }, + "observeSensitiveCommands": { + "type": "boolean" + }, + "autoEncryptOpts": { + "type": "object", + "additionalProperties": false, + "required": [ + "keyVaultNamespace", + "kmsProviders" + ], + "properties": { + "keyVaultNamespace": { + "type": "string" + }, + "bypassAutoEncryption": { + "type": "boolean" + }, + "kmsProviders": { + "$ref": "#/definitions/kmsProviders" + }, + "schemaMap": { + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "extraOptions": { + "type": "object" + }, + "encryptedFieldsMap": { + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "bypassQueryAnalysis": { + "type": "boolean" + }, + "keyExpirationMS": { + "type": "integer" + } + } + }, + "awaitMinPoolSizeMS": { + "type": "integer" + } + } + }, + "clientEncryption": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "clientEncryptionOpts" + ], + "properties": { + "id": { + "type": "string" + }, + "clientEncryptionOpts": { + "$ref": "#/definitions/clientEncryptionOpts" + } + } + }, + "database": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "client", + "databaseName" + ], + "properties": { + "id": { + "type": "string" + }, + "client": { + "type": "string" + }, + "databaseName": { + "type": "string" + }, + "databaseOptions": { + "$ref": "#/definitions/collectionOrDatabaseOptions" + } + } + }, + "collection": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "database", + "collectionName" + ], + "properties": { + "id": { + "type": "string" + }, + "database": { + "type": "string" + }, + "collectionName": { + "type": "string" + }, + "collectionOptions": { + "$ref": "#/definitions/collectionOrDatabaseOptions" + } + } + }, + "session": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "client" + ], + "properties": { + "id": { + "type": "string" + }, + "client": { + "type": "string" + }, + "sessionOptions": { + "type": "object" + } + } + }, + "bucket": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "database" + ], + "properties": { + "id": { + "type": "string" + }, + "database": { + "type": "string" + }, + "bucketOptions": { + "type": "object" + } + } + }, + "thread": { + "type": "object", + "additionalProperties": false, + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + } + } + } + } + }, + "logComponent": { + "type": "string", + "enum": [ + "command", + "topology", + "serverSelection", + "connection" + ] + }, + "spanComponent": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "attributes" + ], + "properties": { + "name": { + "type": "string" + }, + "attributes": { + "type": "object" + }, + "nested": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/spanComponent" + } + } + } + }, + "logSeverityLevel": { + "type": "string", + "enum": [ + "emergency", + "alert", + "critical", + "error", + "warning", + "notice", + "info", + "debug", + "trace" + ] + }, + "clientEncryptionOpts": { + "type": "object", + "additionalProperties": false, + "required": [ + "keyVaultClient", + "keyVaultNamespace", + "kmsProviders" + ], + "properties": { + "keyVaultClient": { + "type": "string" + }, + "keyVaultNamespace": { + "type": "string" + }, + "kmsProviders": { + "$ref": "#/definitions/kmsProviders" + }, + "keyExpirationMS": { + "type": "integer" + } + } + }, + "kmsProviders": { + "$defs": { + "stringOrPlaceholder": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "$$placeholder" + ], + "properties": { + "$$placeholder": {} + } + } + ] + } + }, + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^aws(:[a-zA-Z0-9_]+)?$": { + "type": "object", + "additionalProperties": false, + "properties": { + "accessKeyId": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + }, + "secretAccessKey": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + }, + "sessionToken": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + } + } + }, + "^azure(:[a-zA-Z0-9_]+)?$": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "tenantId": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + }, + "clientId": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + }, + "clientSecret": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + }, + "identityPlatformEndpoint": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "accessToken": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + } + }, + "required": [ + "accessToken" + ] + } + ] + }, + "^gcp(:[a-zA-Z0-9_]+)?$": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "email": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + }, + "privateKey": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + }, + "endpoint": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "accessToken": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + } + }, + "required": [ + "accessToken" + ] + } + ] + }, + "^kmip(:[a-zA-Z0-9_]+)?$": { + "type": "object", + "additionalProperties": false, + "properties": { + "endpoint": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + } + } + }, + "^local(:[a-zA-Z0-9_]+)?$": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "$ref": "#/definitions/kmsProviders/$defs/stringOrPlaceholder" + } + } + } + } + }, + "storeEventsAsEntity": { + "deprecated": true, + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "events" + ], + "properties": { + "id": { + "type": "string" + }, + "events": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "PoolCreatedEvent", + "PoolReadyEvent", + "PoolClearedEvent", + "PoolClosedEvent", + "ConnectionCreatedEvent", + "ConnectionReadyEvent", + "ConnectionClosedEvent", + "ConnectionCheckOutStartedEvent", + "ConnectionCheckOutFailedEvent", + "ConnectionCheckedOutEvent", + "ConnectionCheckedInEvent", + "CommandStartedEvent", + "CommandSucceededEvent", + "CommandFailedEvent", + "ServerDescriptionChangedEvent", + "TopologyDescriptionChangedEvent" + ] + } + } + } + }, + "collectionData": { + "type": "object", + "additionalProperties": false, + "required": [ + "collectionName", + "databaseName", + "documents" + ], + "properties": { + "collectionName": { + "type": "string" + }, + "databaseName": { + "type": "string" + }, + "createOptions": { + "type": "object", + "properties": { + "writeConcern": false + } + }, + "documents": { + "type": "array", + "items": { + "type": "object" + } + } + } + }, + "expectedEventsForClient": { + "type": "object", + "additionalProperties": false, + "required": [ + "client", + "events" + ], + "properties": { + "client": { + "type": "string" + }, + "eventType": { + "type": "string", + "enum": [ + "command", + "cmap", + "sdam" + ] + }, + "events": { + "type": "array" + }, + "ignoreExtraEvents": { + "type": "boolean" + } + }, + "oneOf": [ + { + "required": [ + "eventType" + ], + "properties": { + "eventType": { + "const": "command" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/expectedCommandEvent" + } + } + } + }, + { + "required": [ + "eventType" + ], + "properties": { + "eventType": { + "const": "cmap" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/expectedCmapEvent" + } + } + } + }, + { + "required": [ + "eventType" + ], + "properties": { + "eventType": { + "const": "sdam" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/expectedSdamEvent" + } + } + } + }, + { + "additionalProperties": false, + "properties": { + "client": { + "type": "string" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/expectedCommandEvent" + } + }, + "ignoreExtraEvents": { + "type": "boolean" + } + } + } + ] + }, + "expectedCommandEvent": { + "type": "object", + "additionalProperties": false, + "maxProperties": 1, + "minProperties": 1, + "properties": { + "commandStartedEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "command": { + "type": "object" + }, + "commandName": { + "type": "string" + }, + "databaseName": { + "type": "string" + }, + "hasServiceId": { + "type": "boolean" + }, + "hasServerConnectionId": { + "type": "boolean" + } + } + }, + "commandSucceededEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "reply": { + "type": "object" + }, + "commandName": { + "type": "string" + }, + "databaseName": { + "type": "string" + }, + "hasServiceId": { + "type": "boolean" + }, + "hasServerConnectionId": { + "type": "boolean" + } + } + }, + "commandFailedEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "commandName": { + "type": "string" + }, + "databaseName": { + "type": "string" + }, + "hasServiceId": { + "type": "boolean" + }, + "hasServerConnectionId": { + "type": "boolean" + } + } + } + } + }, + "expectedCmapEvent": { + "type": "object", + "additionalProperties": false, + "maxProperties": 1, + "minProperties": 1, + "properties": { + "poolCreatedEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "poolReadyEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "poolClearedEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "hasServiceId": { + "type": "boolean" + }, + "interruptInUseConnections": { + "type": "boolean" + } + } + }, + "poolClosedEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "connectionCreatedEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "connectionReadyEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "connectionClosedEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "reason": { + "type": "string" + } + } + }, + "connectionCheckOutStartedEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "connectionCheckOutFailedEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "reason": { + "type": "string" + } + } + }, + "connectionCheckedOutEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "connectionCheckedInEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + } + } + }, + "expectedSdamEvent": { + "type": "object", + "additionalProperties": false, + "maxProperties": 1, + "minProperties": 1, + "properties": { + "serverDescriptionChangedEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "previousDescription": { + "$ref": "#/definitions/serverDescription" + }, + "newDescription": { + "$ref": "#/definitions/serverDescription" + } + } + }, + "topologyDescriptionChangedEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "previousDescription": { + "$ref": "#/definitions/topologyDescription" + }, + "newDescription": { + "$ref": "#/definitions/topologyDescription" + } + } + }, + "serverHeartbeatStartedEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "awaited": { + "type": "boolean" + } + } + }, + "serverHeartbeatSucceededEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "awaited": { + "type": "boolean" + } + } + }, + "serverHeartbeatFailedEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "awaited": { + "type": "boolean" + } + } + }, + "topologyOpeningEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "topologyClosedEvent": { + "type": "object", + "additionalProperties": false, + "properties": {} + } + } + }, + "serverDescription": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "Standalone", + "Mongos", + "PossiblePrimary", + "RSPrimary", + "RSSecondary", + "RSOther", + "RSArbiter", + "RSGhost", + "LoadBalancer", + "Unknown" + ] + } + } + }, + "topologyDescription": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "Single", + "Unknown", + "ReplicaSetNoPrimary", + "ReplicaSetWithPrimary", + "Sharded", + "LoadBalanced" + ] + } + } + }, + "expectedLogMessagesForClient": { + "type": "object", + "additionalProperties": false, + "required": [ + "client", + "messages" + ], + "properties": { + "client": { + "type": "string" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/definitions/expectedLogMessage" + } + }, + "ignoreExtraMessages": { + "type": "boolean" + }, + "ignoreMessages": { + "type": "array", + "items": { + "$ref": "#/definitions/expectedLogMessage" + } + } + } + }, + "expectTracingMessagesForClient": { + "additionalProperties": false, + "type": "object", + "required": [ + "client", + "spans" + ], + "properties": { + "client": { + "type": "string" + }, + "ignoreExtraSpans": { + "type": "boolean" + }, + "spans": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/spanComponent" + } + } + } + }, + "expectedLogMessage": { + "type": "object", + "additionalProperties": false, + "required": [ + "level", + "component", + "data" + ], + "properties": { + "level": { + "$ref": "#/definitions/logSeverityLevel" + }, + "component": { + "$ref": "#/definitions/logComponent" + }, + "data": { + "type": "object" + }, + "failureIsRedacted": { + "type": "boolean" + } + } + }, + "collectionOrDatabaseOptions": { + "type": "object", + "additionalProperties": false, + "properties": { + "readConcern": { + "type": "object" + }, + "readPreference": { + "type": "object" + }, + "writeConcern": { + "type": "object" + }, + "timeoutMS": { + "type": "integer" + } + } + }, + "serverApi": { + "type": "object", + "additionalProperties": false, + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string" + }, + "strict": { + "type": "boolean" + }, + "deprecationErrors": { + "type": "boolean" + } + } + }, + "operation": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "object" + ], + "properties": { + "name": { + "type": "string" + }, + "object": { + "type": "string" + }, + "arguments": { + "type": "object" + }, + "ignoreResultAndError": { + "type": "boolean" + }, + "expectError": { + "$ref": "#/definitions/expectedError" + }, + "expectResult": {}, + "saveResultAsEntity": { + "type": "string" + } + }, + "allOf": [ + { + "not": { + "required": [ + "expectError", + "expectResult" + ] + } + }, + { + "not": { + "required": [ + "expectError", + "saveResultAsEntity" + ] + } + }, + { + "not": { + "required": [ + "ignoreResultAndError", + "expectResult" + ] + } + }, + { + "not": { + "required": [ + "ignoreResultAndError", + "expectError" + ] + } + }, + { + "not": { + "required": [ + "ignoreResultAndError", + "saveResultAsEntity" + ] + } + } + ] + }, + "expectedError": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "isError": { + "type": "boolean", + "const": true + }, + "isClientError": { + "type": "boolean" + }, + "isTimeoutError": { + "type": "boolean" + }, + "errorContains": { + "type": "string" + }, + "errorCode": { + "type": "integer" + }, + "errorCodeName": { + "type": "string" + }, + "errorLabelsContain": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "errorLabelsOmit": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "writeErrors": { + "type": "object" + }, + "writeConcernErrors": { + "type": "array", + "items": { + "type": "object" + } + }, + "errorResponse": { + "type": "object" + }, + "expectResult": {} + } + }, + "test": { + "type": "object", + "additionalProperties": false, + "required": [ + "description", + "operations" + ], + "properties": { + "description": { + "type": "string" + }, + "runOnRequirements": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/runOnRequirement" + } + }, + "skipReason": { + "type": "string" + }, + "operations": { + "type": "array", + "items": { + "$ref": "#/definitions/operation" + } + }, + "expectEvents": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/expectedEventsForClient" + } + }, + "expectLogMessages": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/expectedLogMessagesForClient" + } + }, + "expectTracingMessages": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/expectTracingMessagesForClient" + } + }, + "outcome": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/collectionData" + } + } + } + } + } +} diff --git a/source/unified-test-format/tests/valid-pass/operator-gte.json b/source/unified-test-format/tests/valid-pass/operator-gte.json new file mode 100644 index 0000000000..ae69da5c95 --- /dev/null +++ b/source/unified-test-format/tests/valid-pass/operator-gte.json @@ -0,0 +1,88 @@ +{ + "description": "operator-gte", + "schemaVersion": "1.29", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [] + } + ], + "tests": [ + { + "description": "special gte matching operator", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": 2, + "y": 3, + "z": 4 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll0", + "documents": [ + { + "_id": { + "$$gte": 1 + }, + "x": { + "$$gte": 1.9 + }, + "y": { + "$$gte": { + "$numberLong": "3" + } + }, + "z": { + "$$gte": 4 + } + } + ] + }, + "commandName": "insert", + "databaseName": "database0Name" + } + } + ] + } + ] + } + ] +} diff --git a/source/unified-test-format/tests/valid-pass/operator-gte.yml b/source/unified-test-format/tests/valid-pass/operator-gte.yml new file mode 100644 index 0000000000..69ea46597c --- /dev/null +++ b/source/unified-test-format/tests/valid-pass/operator-gte.yml @@ -0,0 +1,41 @@ +description: operator-gte + +# Note: $$gte was introduced as the lower-bound counterpart to $$lte +schemaVersion: "1.29" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0Name + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +tests: + - description: special gte matching operator + operations: + - name: insertOne + object: *collection0 + arguments: + document: { _id : 1, x: 2, y: 3, z: 4 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection0Name + documents: + # We can make exact assertions here but we use the $$gte operator to ensure drivers support it. + - { _id: { $$gte: 1 }, x: { $$gte: 1.9 }, y: { $$gte: { $numberLong: "3"} }, z: { $$gte: 4 } } + commandName: insert + databaseName: *database0Name diff --git a/source/unified-test-format/unified-test-format.md b/source/unified-test-format/unified-test-format.md index 13871503fe..1c28c46058 100644 --- a/source/unified-test-format/unified-test-format.md +++ b/source/unified-test-format/unified-test-format.md @@ -2,7 +2,7 @@ - Status: Accepted - Minimum Server Version: N/A -- Current Schema Version: 1.28.0 +- Current Schema Version: 1.29.0 ______________________________________________________________________ @@ -794,6 +794,10 @@ The structure of this object is as follows: - If `true`, additional unexpected spans are allowed. - If `false` or omitted, the test runner MUST fail if any unexpected spans are detected. + This applies at every level of the span tree, not only to the top-level `spans` array. A span nested under an + expected span, but absent from that span's `nested` array, is an unexpected span. A test can therefore assert that + a command span is *not* nested under a given operation span by omitting it from that operation's `nested` array. + - `spans`: Required array of span objects. Each span describes an expected tracing event. Span object properties: @@ -2931,6 +2935,19 @@ test runner MUST assert that the actual value is less than or equal to the speci the rules specified in [Flexible Numeric Comparisons](#flexible-numeric-comparisons) for this operator. For example, an expected value of `1` would match an actual value of `1.0` and `0.0` but would not match `1.1`. +##### $$gte + +Syntax: + +```yaml +{ $$gte: 1 } +``` + +This operator can be used anywhere a matched value is expected (including [expectResult](#operation_expectResult)). The +test runner MUST assert that the actual value is greater than or equal to the specified value. Test runners MUST also +apply the rules specified in [Flexible Numeric Comparisons](#flexible-numeric-comparisons) for this operator. For +example, an expected value of `1` would match an actual value of `1.0` and `1.1` but would not match `0.0`. + ##### $$matchAsDocument Syntax: @@ -3453,6 +3470,12 @@ other specs *and* collating spec changes developed in parallel or during the sam ## Changelog +- 2026-08-26: **Schema version 1.29.** + + Introduced the `$$gte` operator, the lower-bound counterpart to `$$lte`. Also clarified that `ignoreExtraSpans` + applies at every level of the span tree, so that a test can assert a span is not nested under a given parent by + omitting it from that parent's `nested` array. Both changes require runner support, so they share this version. + - 2026-06-17: Remove pre-4.2 version references. - 2026-03-17: **Schema version 1.28.**