diff --git a/.mci.yml b/.mci.yml index 45867d2f42..f5563a1a77 100644 --- a/.mci.yml +++ b/.mci.yml @@ -331,6 +331,7 @@ functions: export RETRYABLE_READS_TESTS_PATH="$(pwd)/../data/retryable-reads" export READ_WRITE_CONCERN_OPERATION_TESTS_PATH="$(pwd)/../data/read-write-concern/operation" export UNIFIED_FORMAT_TESTS_PATH=$(pwd)/../data/unified-format + export VERSIONED_API_TESTS_PATH=$(pwd)/../data/versioned-api # export environment variables for encryption tests set +o errexit diff --git a/data/unified-format/valid-pass/poc-command-monitoring.json b/data/unified-format/valid-pass/poc-command-monitoring.json index 499396e0ba..591c30ab33 100644 --- a/data/unified-format/valid-pass/poc-command-monitoring.json +++ b/data/unified-format/valid-pass/poc-command-monitoring.json @@ -149,7 +149,7 @@ ] }, "collection": "test", - "batchSize": 1 + "batchSize": 3 }, "commandName": "getMore", "databaseName": "command-monitoring-tests" @@ -219,4 +219,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/data/versioned-api/crud-api-version-1-strict.json b/data/versioned-api/crud-api-version-1-strict.json new file mode 100644 index 0000000000..c24b2ea4c2 --- /dev/null +++ b/data/versioned-api/crud-api-version-1-strict.json @@ -0,0 +1,1110 @@ +{ + "description": "CRUD Api Version 1 (strict)", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.9" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1", + "strict": true + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + }, + { + "database": { + "id": "adminDatabase", + "client": "client", + "databaseName": "admin" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "_yamlAnchors": { + "versions": [ + { + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + ] + }, + "initialData": [ + { + "collectionName": "test", + "databaseName": "versioned-api-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "aggregate on collection appends declared API version", + "operations": [ + { + "name": "aggregate", + "object": "collection", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "aggregate on database appends declared API version", + "operations": [ + { + "name": "aggregate", + "object": "adminDatabase", + "arguments": { + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ] + }, + "expectError": { + "errorCodeName": "APIStrictError" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "bulkWrite appends declared API version", + "operations": [ + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 6, + "x": 66 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteMany": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + }, + { + "updateMany": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 7 + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "ordered": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "x": { + "$nin": [ + 24, + 34 + ] + } + }, + "limit": 0 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 7 + }, + "limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "_id": 4, + "x": 44 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": true + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "countDocuments appends declared API version", + "operations": [ + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "x": { + "$gt": 11 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "x": { + "$gt": 11 + } + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "deleteMany appends declared API version", + "operations": [ + { + "name": "deleteMany", + "object": "collection", + "arguments": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "x": { + "$nin": [ + 24, + 34 + ] + } + }, + "limit": 0 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "deleteOne appends declared API version", + "operations": [ + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 7 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 7 + }, + "limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "distinct appends declared API version", + "operations": [ + { + "name": "distinct", + "object": "collection", + "arguments": { + "fieldName": "x", + "filter": {} + }, + "expectError": { + "isError": true, + "errorContains": "command distinct is not in API Version 1", + "errorCodeName": "APIStrictError" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "x", + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount appends declared API version", + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "arguments": {} + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "find and getMore append API version", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "batchSize": 3 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndDelete appends declared API version", + "operations": [ + { + "name": "findOneAndDelete", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "remove": true, + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndReplace appends declared API version", + "operations": [ + { + "name": "findOneAndReplace", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": { + "x": 33 + }, + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndUpdate appends declared API version", + "operations": [ + { + "name": "findOneAndUpdate", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "insertMany appends declared API version", + "operations": [ + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "insertOne appends declared API version", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 6, + "x": 66 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "replaceOne appends declared API version", + "operations": [ + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "_id": 4, + "x": 44 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": true + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "updateMany appends declared API version", + "operations": [ + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "updateOne appends declared API version", + "operations": [ + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/data/versioned-api/crud-api-version-1.json b/data/versioned-api/crud-api-version-1.json new file mode 100644 index 0000000000..f2c87436a9 --- /dev/null +++ b/data/versioned-api/crud-api-version-1.json @@ -0,0 +1,1102 @@ +{ + "description": "CRUD Api Version 1", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.9" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1", + "deprecationErrors": true + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + }, + { + "database": { + "id": "adminDatabase", + "client": "client", + "databaseName": "admin" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "_yamlAnchors": { + "versions": [ + { + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + ] + }, + "initialData": [ + { + "collectionName": "test", + "databaseName": "versioned-api-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "aggregate on collection appends declared API version", + "operations": [ + { + "name": "aggregate", + "object": "collection", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "aggregate on database appends declared API version", + "operations": [ + { + "name": "aggregate", + "object": "adminDatabase", + "arguments": { + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "bulkWrite appends declared API version", + "operations": [ + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 6, + "x": 66 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteMany": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + }, + { + "updateMany": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 7 + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "ordered": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "x": { + "$nin": [ + 24, + 34 + ] + } + }, + "limit": 0 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 7 + }, + "limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "_id": 4, + "x": 44 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": true + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "countDocuments appends declared API version", + "operations": [ + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "x": { + "$gt": 11 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "x": { + "$gt": 11 + } + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "deleteMany appends declared API version", + "operations": [ + { + "name": "deleteMany", + "object": "collection", + "arguments": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "x": { + "$nin": [ + 24, + 34 + ] + } + }, + "limit": 0 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "deleteOne appends declared API version", + "operations": [ + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 7 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 7 + }, + "limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "distinct appends declared API version", + "operations": [ + { + "name": "distinct", + "object": "collection", + "arguments": { + "fieldName": "x", + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "x", + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount appends declared API version on 4.9.0 or greater", + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "arguments": {} + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "find and getMore append API version", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "batchSize": 3 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "findOneAndDelete appends declared API version", + "operations": [ + { + "name": "findOneAndDelete", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "remove": true, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "findOneAndReplace appends declared API version", + "operations": [ + { + "name": "findOneAndReplace", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": { + "x": 33 + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "findOneAndUpdate appends declared API version", + "operations": [ + { + "name": "findOneAndUpdate", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "insertMany appends declared API version", + "operations": [ + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "insertOne appends declared API version", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 6, + "x": 66 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "replaceOne appends declared API version", + "operations": [ + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "_id": 4, + "x": 44 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": true + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "updateMany appends declared API version", + "operations": [ + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "updateOne appends declared API version", + "operations": [ + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/data/versioned-api/runcommand-helper-no-api-version-declared.json b/data/versioned-api/runcommand-helper-no-api-version-declared.json new file mode 100644 index 0000000000..3e40681474 --- /dev/null +++ b/data/versioned-api/runcommand-helper-no-api-version-declared.json @@ -0,0 +1,117 @@ +{ + "description": "RunCommand helper: No API version declared", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.9", + "serverParameters": { + "requireApiVersion": false + } + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + } + ], + "tests": [ + { + "description": "runCommand does not inspect or change the command document", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1, + "apiVersion": "server_will_never_support_this_api_version" + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "apiVersion": "server_will_never_support_this_api_version", + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + }, + "commandName": "ping", + "databaseName": "versioned-api-tests" + } + } + ] + } + ] + }, + { + "description": "runCommand does not prevent sending invalid API version declarations", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1, + "apiStrict": true + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "apiVersion": { + "$$exists": false + }, + "apiStrict": true, + "apiDeprecationErrors": { + "$$exists": false + } + }, + "commandName": "ping", + "databaseName": "versioned-api-tests" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/data/versioned-api/test-commands-deprecation-errors.json b/data/versioned-api/test-commands-deprecation-errors.json new file mode 100644 index 0000000000..e1482229c9 --- /dev/null +++ b/data/versioned-api/test-commands-deprecation-errors.json @@ -0,0 +1,74 @@ +{ + "description": "Test commands: deprecation errors", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.9", + "serverParameters": { + "enableTestCommands": true, + "acceptApiVersion2": true, + "requireApiVersion": false + } + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + } + ], + "tests": [ + { + "description": "Running a command that is deprecated raises a deprecation error", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "testDeprecationInVersion2", + "command": { + "testDeprecationInVersion2": 1, + "apiVersion": "2", + "apiDeprecationErrors": true + } + }, + "expectError": { + "isError": true, + "errorContains": "command testDeprecationInVersion2 is deprecated in API Version 2", + "errorCodeName": "APIDeprecationError" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "testDeprecationInVersion2": 1, + "apiVersion": "2", + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/data/versioned-api/test-commands-strict-mode.json b/data/versioned-api/test-commands-strict-mode.json new file mode 100644 index 0000000000..e905b67843 --- /dev/null +++ b/data/versioned-api/test-commands-strict-mode.json @@ -0,0 +1,74 @@ +{ + "description": "Test commands: strict mode", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.9", + "serverParameters": { + "enableTestCommands": true + } + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1", + "strict": true + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + } + ], + "tests": [ + { + "description": "Running a command that is not part of the versioned API results in an error", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "testVersion2", + "command": { + "testVersion2": 1 + } + }, + "expectError": { + "isError": true, + "errorContains": "command testVersion2 is not in API Version 1", + "errorCodeName": "APIStrictError" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "testVersion2": 1, + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/data/versioned-api/test_files.txt b/data/versioned-api/test_files.txt new file mode 100644 index 0000000000..4da72c2438 --- /dev/null +++ b/data/versioned-api/test_files.txt @@ -0,0 +1,6 @@ +crud-api-version-1-strict.json +crud-api-version-1.json +runcommand-helper-no-api-version-declared.json +test-commands-deprecation-errors.json +test-commands-strict-mode.json +transaction-handling.json \ No newline at end of file diff --git a/data/versioned-api/transaction-handling.json b/data/versioned-api/transaction-handling.json new file mode 100644 index 0000000000..5c627bb351 --- /dev/null +++ b/data/versioned-api/transaction-handling.json @@ -0,0 +1,348 @@ +{ + "description": "Transaction handling", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.9", + "topologies": [ + "replicaset", + "sharded-replicaset", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + }, + { + "session": { + "id": "session", + "client": "client" + } + } + ], + "_yamlAnchors": { + "versions": [ + { + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + ] + }, + "initialData": [ + { + "collectionName": "test", + "databaseName": "versioned-api-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "All commands in a transaction declare an API version", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded-replicaset", + "load-balanced" + ] + } + ], + "operations": [ + { + "name": "startTransaction", + "object": "session" + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 6, + "x": 66 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 6 + } + } + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 7, + "x": 77 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 7 + } + } + } + }, + { + "name": "commitTransaction", + "object": "session" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "lsid": { + "$$sessionLsid": "session" + }, + "startTransaction": true, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 7, + "x": 77 + } + ], + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "abortTransaction includes an API version", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded-replicaset", + "load-balanced" + ] + } + ], + "operations": [ + { + "name": "startTransaction", + "object": "session" + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 6, + "x": 66 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 6 + } + } + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 7, + "x": 77 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 7 + } + } + } + }, + { + "name": "abortTransaction", + "object": "session" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "lsid": { + "$$sessionLsid": "session" + }, + "startTransaction": true, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 7, + "x": 77 + } + ], + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/mongocxx/CMakeLists.txt b/src/mongocxx/CMakeLists.txt index 2157d0066c..19405a359d 100644 --- a/src/mongocxx/CMakeLists.txt +++ b/src/mongocxx/CMakeLists.txt @@ -156,6 +156,7 @@ set(mongocxx_sources options/insert.cpp options/pool.cpp options/replace.cpp + options/server_api.cpp options/tls.cpp options/transaction.cpp options/update.cpp @@ -399,10 +400,13 @@ set_local_dist (src_mongocxx_DIST_local options/pool.cpp options/pool.hpp options/private/apm.hh + options/private/server_api.hh options/private/ssl.hh options/private/transaction.hh options/replace.cpp options/replace.hpp + options/server_api.cpp + options/server_api.hpp options/ssl.hpp options/tls.cpp options/tls.hpp diff --git a/src/mongocxx/client.cpp b/src/mongocxx/client.cpp index 0dffa1ee27..7757ae4406 100644 --- a/src/mongocxx/client.cpp +++ b/src/mongocxx/client.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,19 @@ client::client(const class uri& uri, const options::client& options) { } } + if (options.server_api_opts()) { + const auto& server_api_opts = *options.server_api_opts(); + auto mongoc_server_api_opts = options::make_server_api(server_api_opts); + + bson_error_t error; + auto result = libmongoc::client_set_server_api( + _get_impl().client_t, mongoc_server_api_opts.get(), &error); + + if (!result) { + throw_exception(error); + } + } + #if defined(MONGOCXX_ENABLE_SSL) && defined(MONGOC_ENABLE_SSL) if (options.tls_opts()) { auto mongoc_opts = options::make_tls_opts(*options.tls_opts()); diff --git a/src/mongocxx/options/client.cpp b/src/mongocxx/options/client.cpp index 4cca1d3cc1..e81c143782 100644 --- a/src/mongocxx/options/client.cpp +++ b/src/mongocxx/options/client.cpp @@ -55,6 +55,15 @@ const stdx::optional& client::auto_encryption_opts() const { return _auto_encrypt_opts; } +client& client::server_api_opts(server_api server_api_opts) { + _server_api_opts = std::move(server_api_opts); + return *this; +} + +const stdx::optional& client::server_api_opts() const { + return _server_api_opts; +} + } // namespace options MONGOCXX_INLINE_NAMESPACE_END } // namespace mongocxx diff --git a/src/mongocxx/options/client.hpp b/src/mongocxx/options/client.hpp index 3d0a3a3afa..9ef61d06bb 100644 --- a/src/mongocxx/options/client.hpp +++ b/src/mongocxx/options/client.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -116,10 +117,32 @@ class MONGOCXX_API client { /// const stdx::optional& apm_opts() const; + /// + /// Sets the server API options. + /// + /// @param server_api_opts + /// The options for server API. + /// + /// @return + /// A reference to the object on which this member function is being called. This facilitates + /// method chaining. + /// + client& server_api_opts(server_api server_api_opts); + + /// + /// Gets the current server API options or returns a disengaged optional if there are no server + /// API options set. + /// + /// @return + /// The server API options. + /// + const stdx::optional& server_api_opts() const; + private: stdx::optional _tls_opts; stdx::optional _apm_opts; stdx::optional _auto_encrypt_opts; + stdx::optional _server_api_opts; }; } // namespace options diff --git a/src/mongocxx/options/private/server_api.hh b/src/mongocxx/options/private/server_api.hh new file mode 100644 index 0000000000..19f44fbe7c --- /dev/null +++ b/src/mongocxx/options/private/server_api.hh @@ -0,0 +1,60 @@ +// Copyright 2021 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. + +#pragma once + +#include + +#include +#include + +namespace mongocxx { +MONGOCXX_INLINE_NAMESPACE_BEGIN +namespace options { + +using unique_server_api = + std::unique_ptr; + +static unique_server_api make_server_api(const server_api& opts) { + mongoc_server_api_version_t mongoc_api_version; + + // Convert version enum value to std::string then to c_str to create mongoc api version. + auto result = libmongoc::server_api_version_from_string( + server_api::version_to_string(opts.get_version()).c_str(), &mongoc_api_version); + if (!result) { + throw std::logic_error{"invalid server API version" + + server_api::version_to_string(opts.get_version())}; + } + + auto mongoc_server_api_opts = libmongoc::server_api_new(mongoc_api_version); + if (!mongoc_server_api_opts) { + throw std::logic_error{"could not create server API"}; + } + + if (opts.strict().value_or(false)) { + libmongoc::server_api_strict(mongoc_server_api_opts, opts.strict().value_or(false)); + } + if (opts.deprecation_errors().value_or(false)) { + libmongoc::server_api_deprecation_errors(mongoc_server_api_opts, + opts.deprecation_errors().value_or(false)); + } + + return {mongoc_server_api_opts, libmongoc::server_api_destroy}; +} + +} // namespace options +MONGOCXX_INLINE_NAMESPACE_END +} // namespace mongocxx + +#include diff --git a/src/mongocxx/options/server_api.cpp b/src/mongocxx/options/server_api.cpp new file mode 100644 index 0000000000..fca84fcb95 --- /dev/null +++ b/src/mongocxx/options/server_api.cpp @@ -0,0 +1,69 @@ +// Copyright 2021 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. + +#include + +#include +#include +#include +#include +#include + +namespace mongocxx { +MONGOCXX_INLINE_NAMESPACE_BEGIN +namespace options { + +std::string server_api::version_to_string(server_api::version version) { + switch (version) { + case server_api::version::k_version_1: + return "1"; + default: + throw std::logic_error{"invalid server API version"}; + } +} + +server_api::version server_api::version_from_string(stdx::string_view version) { + if (!version.compare("1")) { + return server_api::version::k_version_1; + } + throw std::logic_error{"invalid server API version"}; +} + +server_api::server_api(server_api::version version) : _version(std::move(version)) {} + +server_api& server_api::strict(bool strict) { + _strict = strict; + return *this; +} + +const stdx::optional& server_api::strict() const { + return _strict; +} + +server_api& server_api::deprecation_errors(bool deprecation_errors) { + _deprecation_errors = deprecation_errors; + return *this; +} + +const stdx::optional& server_api::deprecation_errors() const { + return _deprecation_errors; +} + +server_api::version server_api::get_version() const { + return _version; +} + +} // namespace options +MONGOCXX_INLINE_NAMESPACE_END +} // namespace mongocxx diff --git a/src/mongocxx/options/server_api.hpp b/src/mongocxx/options/server_api.hpp new file mode 100644 index 0000000000..de3aec0490 --- /dev/null +++ b/src/mongocxx/options/server_api.hpp @@ -0,0 +1,143 @@ +// Copyright 2021 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. + +#pragma once + +#include + +#include + +#include +#include +#include + +namespace mongocxx { +MONGOCXX_INLINE_NAMESPACE_BEGIN + +class client; +class pool; + +namespace options { + +/// +/// Class representing options for server API. +/// +class MONGOCXX_API server_api { + public: + /// + /// Enum representing the possible values for server API version. + /// + enum class version { k_version_1 }; + + /// + /// Constructs a new server_api object. + /// + /// The specified API version will be sent to the server. This will cause + /// the server to behave in a manner compatible with that API version. + /// The driver will behave in a manner compatible with a server configured + /// with that API version, regardless of the server's actual release version. + /// + /// @param version + /// The server api version to send to the server. + /// + server_api(version version); + + /// + /// Converts a version enum value to its string value. + /// + /// @param version + /// The enum value to convert to a string. + /// + /// @throws mongocxx::logic_error on an invalid argument + /// + /// @return + /// The string value of the given enum value. + /// + static std::string version_to_string(version version); + + /// + /// Converts a version string to its enum value. + /// + /// @param version + /// The string to convert to an enum value. + /// + /// @throws mongocxx::logic_error on an invalid argument + /// + /// @return + /// The enum value of the given string. + /// + static version version_from_string(stdx::string_view version); + + /// + /// Sets the strict option, specifying whether the server should return + /// errors for features that are not part of the declared API version. + /// + /// @param strict + /// The value to set strict to. + /// + /// @return + /// A reference to this object to facilitate method chaining. + /// + server_api& strict(bool strict); + + /// + /// Gets the current value of the strict option. + /// + /// @return + /// The optional value of the strict option. + /// + const stdx::optional& strict() const; + + /// + /// Sets the deprecation errors option, specifying whether the server should + /// return errors for features that are deprecated in the declared API version. + /// + /// @param deprecation_errors + /// The value to set deprecation errors to. + /// + /// @return + /// A reference to this object to facilitate method chaining. + /// + server_api& deprecation_errors(bool deprecation_errors); + + /// + /// Gets the current value of the deprecation errors option. + /// + /// @return + /// The optional value of the deprecation errors option. + /// + const stdx::optional& deprecation_errors() const; + + /// + /// Gets the declared server api version. + /// + /// @return + /// The version enum value specifying the declared server api version. + /// + version get_version() const; + + private: + friend class mongocxx::client; + friend class mongocxx::pool; + + version _version; + stdx::optional _strict; + stdx::optional _deprecation_errors; +}; + +} // namespace options +MONGOCXX_INLINE_NAMESPACE_END +} // namespace mongocxx + +#include diff --git a/src/mongocxx/pool.cpp b/src/mongocxx/pool.cpp index 47e0d58bba..8ee0cc8c9a 100644 --- a/src/mongocxx/pool.cpp +++ b/src/mongocxx/pool.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,19 @@ pool::pool(const uri& uri, const options::pool& options) auto context = static_cast(&(_impl->listeners)); libmongoc::client_pool_set_apm_callbacks(_impl->client_pool_t, callbacks.get(), context); } + + if (options.client_opts().server_api_opts()) { + const auto& server_api_opts = *options.client_opts().server_api_opts(); + auto mongoc_server_api_opts = options::make_server_api(server_api_opts); + + bson_error_t error; + auto result = libmongoc::client_pool_set_server_api( + _impl->client_pool_t, mongoc_server_api_opts.get(), &error); + + if (!result) { + throw_exception(error); + } + } } client* pool::entry::operator->() const& noexcept { diff --git a/src/mongocxx/private/libmongoc_symbols.hh b/src/mongocxx/private/libmongoc_symbols.hh index 9afce8ff99..018fc9317b 100644 --- a/src/mongocxx/private/libmongoc_symbols.hh +++ b/src/mongocxx/private/libmongoc_symbols.hh @@ -152,6 +152,7 @@ MONGOCXX_LIBMONGOC_SYMBOL(client_pool_new) MONGOCXX_LIBMONGOC_SYMBOL(client_pool_pop) MONGOCXX_LIBMONGOC_SYMBOL(client_pool_push) MONGOCXX_LIBMONGOC_SYMBOL(client_pool_set_apm_callbacks) +MONGOCXX_LIBMONGOC_SYMBOL(client_pool_set_server_api) MONGOCXX_LIBMONGOC_SYMBOL(client_pool_try_pop) MONGOCXX_LIBMONGOC_SYMBOL(client_reset) MONGOCXX_LIBMONGOC_SYMBOL(client_select_server) @@ -175,6 +176,7 @@ MONGOCXX_LIBMONGOC_SYMBOL(client_set_apm_callbacks) MONGOCXX_LIBMONGOC_SYMBOL(client_set_read_concern) MONGOCXX_LIBMONGOC_SYMBOL(client_set_read_prefs) MONGOCXX_LIBMONGOC_SYMBOL(client_set_write_concern) +MONGOCXX_LIBMONGOC_SYMBOL(client_set_server_api) MONGOCXX_LIBMONGOC_SYMBOL(client_start_session) MONGOCXX_LIBMONGOC_SYMBOL(client_watch) MONGOCXX_LIBMONGOC_SYMBOL(collection_aggregate) @@ -263,6 +265,16 @@ MONGOCXX_LIBMONGOC_SYMBOL(read_prefs_set_hedge) MONGOCXX_LIBMONGOC_SYMBOL(read_prefs_set_max_staleness_seconds) MONGOCXX_LIBMONGOC_SYMBOL(read_prefs_set_mode) MONGOCXX_LIBMONGOC_SYMBOL(read_prefs_set_tags) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_version_to_string) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_version_from_string) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_new) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_copy) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_destroy) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_strict) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_deprecation_errors) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_get_deprecation_errors) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_get_strict) +MONGOCXX_LIBMONGOC_SYMBOL(server_api_get_version) MONGOCXX_LIBMONGOC_SYMBOL(server_description_host) MONGOCXX_LIBMONGOC_SYMBOL(server_description_id) MONGOCXX_LIBMONGOC_SYMBOL(server_description_hello_response) diff --git a/src/mongocxx/test/CMakeLists.txt b/src/mongocxx/test/CMakeLists.txt index 9565937244..2a8dd1bde7 100644 --- a/src/mongocxx/test/CMakeLists.txt +++ b/src/mongocxx/test/CMakeLists.txt @@ -262,6 +262,8 @@ set_tests_properties(unified_format_spec PROPERTIES ENVIRONMENT "UNIFIED_FORMAT_TESTS_PATH=${PROJECT_SOURCE_DIR}/../../data/unified-format") set_property(TEST unified_format_spec APPEND PROPERTY ENVIRONMENT "CRUD_UNIFIED_TESTS_PATH=${PROJECT_SOURCE_DIR}/../../data/crud/unified") +set_property(TEST unified_format_spec + APPEND PROPERTY ENVIRONMENT "VERSIONED_API_TESTS_PATH=${PROJECT_SOURCE_DIR}/../../data/versioned-api") if (MONGOCXX_ENABLE_SLOW_TESTS) set_property(TEST driver diff --git a/src/mongocxx/test/spec/unified_tests/runner.cpp b/src/mongocxx/test/spec/unified_tests/runner.cpp index 8fe15222ab..f8f5e261ee 100644 --- a/src/mongocxx/test/spec/unified_tests/runner.cpp +++ b/src/mongocxx/test/spec/unified_tests/runner.cpp @@ -284,27 +284,27 @@ void add_ignore_command_monitoring_events(document::view object) { } } -/* TODO CXX-2138: Actually implement add_server_api below with new server_api options class. -void add_server_api(options::server_api& sapi_opts, document::view object) { - if (!object["serverApi"]) - return; - - if (auto sav = object["serverApi"]["version"]) { - REQUIRE(sav.type() == type::k_string); - sapi_opts.version(sav); - } else { +options::server_api create_server_api(document::view object) { + document::element sav; + if (!(sav = object["serverApi"]["version"])) { FAIL("must specify a version when using serverApi"); } + REQUIRE(sav.type() == type::k_string); + auto version = options::server_api::version_from_string(sav.get_string().value); + auto server_api_opts = options::server_api(version); + if (auto de = object["serverApi"]["deprecationErrors"]) { REQUIRE(de.type() == type::k_bool); - sapi_opts.deprecation_errors(de); + server_api_opts.deprecation_errors(de.get_bool()); } if (auto strict = object["serverApi"]["strict"]) { REQUIRE(strict.type() == type::k_bool); - sapi_opts.strict(strict); + server_api_opts.strict(strict.get_bool()); } -} */ + + return server_api_opts; +} write_concern get_write_concern(const document::element& opts) { if (!opts["writeConcern"]) @@ -423,15 +423,18 @@ database create_database(document::view object) { client create_client(document::view object) { auto conn = "mongodb://" + get_hostnames(object) + "/?" + uri_options_to_string(object); - auto opts = options::apm{}; + auto apm_opts = options::apm{}; + auto client_opts = options::client{}; + if (object["serverApi"]) { + auto server_api_opts = create_server_api(object); + client_opts.server_api_opts(server_api_opts); + } - add_observe_events(opts, object); + add_observe_events(apm_opts, object); add_ignore_command_monitoring_events(object); - // TODO CXX-2138: Actually add server api options to client. - // add_server_api(server_api_opts, object); CAPTURE(conn); - return client{uri{conn}, options::client{}.apm_opts(opts)}; + return client{uri{conn}, client_opts.apm_opts(apm_opts)}; } bool add_to_map(const array::element& obj) { @@ -591,8 +594,13 @@ void assert_error(const mongocxx::operation_exception& exception, REQUIRE(actual_code == expected_code.get_int32()); } + if (auto code_name = expect_error["errorCodeName"]) { + auto expected_name = string::to_string(code_name.get_string().value); + uint32_t expected_code = error_code_from_name(expected_name); + REQUIRE(exception.code().value() == static_cast(expected_code)); + } + REQUIRE_FALSE(/* TODO */ expect_error["errorContains"]); - REQUIRE_FALSE(/* TODO */ expect_error["errorCodeName"]); } void assert_error(mongocxx::exception& e, const array::element& ops) { @@ -829,7 +837,6 @@ TEST_CASE("CRUD unified format spec automated tests", "[unified_format_spec]") { } } -/* TODO CXX-2138: Uncomment versioned API spec TEST_CASE below. TEST_CASE("versioned API spec automated tests", "[unified_format_spec]") { instance::current(); @@ -844,5 +851,5 @@ TEST_CASE("versioned API spec automated tests", "[unified_format_spec]") { CAPTURE(file); run_tests_in_file(path + '/' + file); } -} */ +} } // namespace diff --git a/src/mongocxx/test/spec/util.cpp b/src/mongocxx/test/spec/util.cpp index 9fc5b93bc3..15fb6af6b5 100644 --- a/src/mongocxx/test/spec/util.cpp +++ b/src/mongocxx/test/spec/util.cpp @@ -75,6 +75,8 @@ uint32_t error_code_from_name(string_view name) { return 100; } else if (name.compare("OperationNotSupportedInTransaction") == 0) { return 263; + } else if (name.compare("APIStrictError") == 0) { + return 323; } return 0; diff --git a/src/mongocxx/test/spec/util.hh b/src/mongocxx/test/spec/util.hh index 8396a63980..c973be6ba8 100644 --- a/src/mongocxx/test/spec/util.hh +++ b/src/mongocxx/test/spec/util.hh @@ -31,6 +31,11 @@ namespace spec { using namespace bsoncxx; using namespace mongocxx; +/// +/// Returns corresponding error code from given error name. +/// +uint32_t error_code_from_name(stdx::string_view name); + /// /// Returns true if this test should be skipped for any reason (for /// example, if a skipReason is defined, or if the given topology is not