diff --git a/src/coll/options.rs b/src/coll/options.rs index 8987eac0f..983d1ca4e 100644 --- a/src/coll/options.rs +++ b/src/coll/options.rs @@ -490,6 +490,15 @@ pub struct AggregateOptions { /// If none is specified, the write concern defined on the object executing this operation will /// be used. pub write_concern: Option, + + /// A document with any amount of parameter names, each followed by definitions of constants in + /// the MQL Aggregate Expression language. Each parameter name is then usable to access the + /// value of the corresponding MQL Expression with the "$$" syntax within Aggregate Expression + /// contexts. + /// + /// This feature is only available on server versions 5.0 and above. + #[serde(rename = "let")] + pub let_vars: Option, } /// Specifies the options to a diff --git a/src/test/spec/json/crud/unified/aggregate-let.json b/src/test/spec/json/crud/unified/aggregate-let.json new file mode 100644 index 000000000..4ce8256cb --- /dev/null +++ b/src/test/spec/json/crud/unified/aggregate-let.json @@ -0,0 +1,478 @@ +{ + "description": "aggregate-let", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + }, + { + "collection": { + "id": "collection1", + "database": "database0", + "collectionName": "coll1" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1 + } + ] + }, + { + "collectionName": "coll1", + "databaseName": "crud-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "Aggregate with let option", + "runOnRequirements": [ + { + "minServerVersion": "5.0" + } + ], + "operations": [ + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + } + }, + { + "$project": { + "_id": 0, + "x": "$$x", + "y": "$$y", + "rand": "$$rand" + } + } + ], + "let": { + "id": 1, + "x": "foo", + "y": { + "$literal": "bar" + }, + "rand": { + "$rand": {} + } + } + }, + "expectResult": [ + { + "x": "foo", + "y": "bar", + "rand": { + "$$type": "double" + } + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "pipeline": [ + { + "$match": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + } + }, + { + "$project": { + "_id": 0, + "x": "$$x", + "y": "$$y", + "rand": "$$rand" + } + } + ], + "let": { + "id": 1, + "x": "foo", + "y": { + "$literal": "bar" + }, + "rand": { + "$rand": {} + } + } + } + } + } + ] + } + ] + }, + { + "description": "Aggregate with let option and dollar-prefixed $literal value", + "runOnRequirements": [ + { + "minServerVersion": "5.0", + "topologies": [ + "single", + "replicaset" + ] + } + ], + "operations": [ + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + } + }, + { + "$project": { + "_id": 0, + "x": "$$x", + "y": "$$y", + "rand": "$$rand" + } + } + ], + "let": { + "id": 1, + "x": "foo", + "y": { + "$literal": "$bar" + }, + "rand": { + "$rand": {} + } + } + }, + "expectResult": [ + { + "x": "foo", + "y": "$bar", + "rand": { + "$$type": "double" + } + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "pipeline": [ + { + "$match": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + } + }, + { + "$project": { + "_id": 0, + "x": "$$x", + "y": "$$y", + "rand": "$$rand" + } + } + ], + "let": { + "id": 1, + "x": "foo", + "y": { + "$literal": "$bar" + }, + "rand": { + "$rand": {} + } + } + } + } + } + ] + } + ] + }, + { + "description": "Aggregate with let option unsupported (server-side error)", + "runOnRequirements": [ + { + "minServerVersion": "2.6.0", + "maxServerVersion": "4.4.99" + } + ], + "operations": [ + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ], + "let": { + "x": "foo" + } + }, + "expectError": { + "errorContains": "unrecognized field 'let'", + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ], + "let": { + "x": "foo" + } + } + } + } + ] + } + ] + }, + { + "description": "Aggregate to collection with let option", + "runOnRequirements": [ + { + "minServerVersion": "5.0" + } + ], + "operations": [ + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + } + }, + { + "$project": { + "_id": 1 + } + }, + { + "$out": "coll1" + } + ], + "let": { + "id": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "pipeline": [ + { + "$match": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + } + }, + { + "$project": { + "_id": 1 + } + }, + { + "$out": "coll1" + } + ], + "let": { + "id": 1 + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll1", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "Aggregate to collection with let option unsupported (server-side error)", + "runOnRequirements": [ + { + "minServerVersion": "2.6.0", + "maxServerVersion": "4.4.99" + } + ], + "operations": [ + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + } + }, + { + "$project": { + "_id": 1 + } + }, + { + "$out": "coll1" + } + ], + "let": { + "id": 1 + } + }, + "expectError": { + "errorContains": "unrecognized field 'let'", + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "pipeline": [ + { + "$match": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + } + }, + { + "$project": { + "_id": 1 + } + }, + { + "$out": "coll1" + } + ], + "let": { + "id": 1 + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/aggregate-let.yml b/src/test/spec/json/crud/unified/aggregate-let.yml new file mode 100644 index 000000000..ed34c37c4 --- /dev/null +++ b/src/test/spec/json/crud/unified/aggregate-let.yml @@ -0,0 +1,173 @@ +description: "aggregate-let" + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + - collection: + id: &collection1 collection1 + database: *database0 + collectionName: &collection1Name coll1 + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1 } + - collectionName: *collection1Name + databaseName: *database0Name + documents: [ ] + +tests: + # TODO: Once SERVER-57403 is resolved, this test can be removed in favor of + # the "dollar-prefixed $literal value" test below. + - description: "Aggregate with let option" + runOnRequirements: + - minServerVersion: "5.0" + operations: + - name: aggregate + object: *collection0 + arguments: + pipeline: &pipeline0 + # $match takes a query expression, so $expr is necessary to utilize + # an aggregate expression context and access "let" variables. + - $match: { $expr: { $eq: ["$_id", "$$id"] } } + - $project: { _id: 0, x: "$$x", y: "$$y", rand: "$$rand" } + # Values in "let" must be constant or closed expressions that do not + # depend on document values. This test demonstrates a basic constant + # value, a value wrapped with $literal (to avoid expression parsing), + # and a closed expression (e.g. $rand). + let: &let0 + id: 1 + x: foo + y: { $literal: "bar" } + rand: { $rand: {} } + expectResult: + - { x: "foo", y: "bar", rand: { $$type: "double" } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0Name + pipeline: *pipeline0 + let: *let0 + + - description: "Aggregate with let option and dollar-prefixed $literal value" + runOnRequirements: + - minServerVersion: "5.0" + # TODO: Remove topology restrictions once SERVER-57403 is resolved + topologies: ["single", "replicaset"] + operations: + - name: aggregate + object: *collection0 + arguments: + pipeline: &pipeline0 + # $match takes a query expression, so $expr is necessary to utilize + # an aggregate expression context and access "let" variables. + - $match: { $expr: { $eq: ["$_id", "$$id"] } } + - $project: { _id: 0, x: "$$x", y: "$$y", rand: "$$rand" } + # Values in "let" must be constant or closed expressions that do not + # depend on document values. This test demonstrates a basic constant + # value, a value wrapped with $literal (to avoid expression parsing), + # and a closed expression (e.g. $rand). + let: &let0 + id: 1 + x: foo + y: { $literal: "$bar" } + rand: { $rand: {} } + expectResult: + - { x: "foo", y: "$bar", rand: { $$type: "double" } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0Name + pipeline: *pipeline0 + let: *let0 + + - description: "Aggregate with let option unsupported (server-side error)" + runOnRequirements: + - minServerVersion: "2.6.0" + maxServerVersion: "4.4.99" + operations: + - name: aggregate + object: *collection0 + arguments: + pipeline: &pipeline1 + - $match: { _id: 1 } + let: &let1 + x: foo + expectError: + # Older server versions may not report an error code, but the error + # message is consistent between 2.6.x and 4.4.x server versions. + errorContains: "unrecognized field 'let'" + isClientError: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0Name + pipeline: *pipeline1 + let: *let1 + + - description: "Aggregate to collection with let option" + runOnRequirements: + - minServerVersion: "5.0" + operations: + - name: aggregate + object: *collection0 + arguments: + pipeline: &pipeline2 + - $match: { $expr: { $eq: ["$_id", "$$id"] } } + - $project: { _id: 1 } + - $out: *collection1Name + let: &let2 + id: 1 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0Name + pipeline: *pipeline2 + let: *let2 + outcome: + - collectionName: *collection1Name + databaseName: *database0Name + documents: + - { _id: 1 } + + - description: "Aggregate to collection with let option unsupported (server-side error)" + runOnRequirements: + - minServerVersion: "2.6.0" + maxServerVersion: "4.4.99" + operations: + - name: aggregate + object: *collection0 + arguments: + pipeline: *pipeline2 + let: *let2 + expectError: + errorContains: "unrecognized field 'let'" + isClientError: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0Name + pipeline: *pipeline2 + let: *let2