1// Copyright (C) MongoDB, Inc. 2017-present.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
7package unified
8
9import (
10	"context"
11	"fmt"
12
13	"go.mongodb.org/mongo-driver/bson"
14	"go.mongodb.org/mongo-driver/mongo"
15)
16
17type Operation struct {
18	Name           string         `bson:"name"`
19	Object         string         `bson:"object"`
20	Arguments      bson.Raw       `bson:"arguments"`
21	ExpectedError  *ExpectedError `bson:"expectError"`
22	ExpectedResult *bson.RawValue `bson:"expectResult"`
23	ResultEntityID *string        `bson:"saveResultAsEntity"`
24}
25
26// Execute runs the operation and verifies the returned result and/or error. If the result needs to be saved as
27// an entity, it also updates the EntityMap associated with ctx to do so.
28func (op *Operation) Execute(ctx context.Context) error {
29	res, err := op.run(ctx)
30	if err != nil {
31		return fmt.Errorf("execution failed: %v", err)
32	}
33
34	if err := VerifyOperationError(ctx, op.ExpectedError, res); err != nil {
35		return fmt.Errorf("error verification failed: %v", err)
36	}
37
38	if op.ExpectedResult != nil {
39		if err := VerifyOperationResult(ctx, *op.ExpectedResult, res); err != nil {
40			return fmt.Errorf("result verification failed: %v", err)
41		}
42	}
43	return nil
44}
45
46func (op *Operation) run(ctx context.Context) (*OperationResult, error) {
47	if op.Object == "testRunner" {
48		// testRunner operations don't have results or expected errors, so we use NewEmptyResult to fake a result.
49		return NewEmptyResult(), executeTestRunnerOperation(ctx, op)
50	}
51
52	// Special handling for the "session" field because it applies to all operations.
53	if id, ok := op.Arguments.Lookup("session").StringValueOK(); ok {
54		sess, err := Entities(ctx).Session(id)
55		if err != nil {
56			return nil, err
57		}
58		ctx = mongo.NewSessionContext(ctx, sess)
59
60		// Set op.Arguments to a new document that has the "session" field removed so individual operations do
61		// not have to account for it.
62		op.Arguments = RemoveFieldsFromDocument(op.Arguments, "session")
63	}
64
65	switch op.Name {
66	// Session operations
67	case "abortTransaction":
68		return executeAbortTransaction(ctx, op)
69	case "commitTransaction":
70		return executeCommitTransaction(ctx, op)
71	case "endSession":
72		// The EndSession() method doesn't return a result, so we return a non-nil empty result.
73		return NewEmptyResult(), executeEndSession(ctx, op)
74	case "startTransaction":
75		return executeStartTransaction(ctx, op)
76	case "withTransaction":
77		// executeWithTransaction internally verifies results/errors for each operation, so it doesn't return a result.
78		return NewEmptyResult(), executeWithTransaction(ctx, op)
79
80	// Client operations
81	case "createChangeStream":
82		return executeCreateChangeStream(ctx, op)
83	case "listDatabases":
84		return executeListDatabases(ctx, op)
85
86	// Database operations
87	case "createCollection":
88		return executeCreateCollection(ctx, op)
89	case "dropCollection":
90		return executeDropCollection(ctx, op)
91	case "listCollections":
92		return executeListCollections(ctx, op)
93	case "listCollectionNames":
94		return executeListCollectionNames(ctx, op)
95	case "runCommand":
96		return executeRunCommand(ctx, op)
97
98	// Collection operations
99	case "aggregate":
100		return executeAggregate(ctx, op)
101	case "bulkWrite":
102		return executeBulkWrite(ctx, op)
103	case "countDocuments":
104		return executeCountDocuments(ctx, op)
105	case "createIndex":
106		return executeCreateIndex(ctx, op)
107	case "deleteOne":
108		return executeDeleteOne(ctx, op)
109	case "deleteMany":
110		return executeDeleteMany(ctx, op)
111	case "distinct":
112		return executeDistinct(ctx, op)
113	case "estimatedDocumentCount":
114		return executeEstimatedDocumentCount(ctx, op)
115	case "find":
116		return executeFind(ctx, op)
117	case "findOneAndUpdate":
118		return executeFindOneAndUpdate(ctx, op)
119	case "insertMany":
120		return executeInsertMany(ctx, op)
121	case "insertOne":
122		return executeInsertOne(ctx, op)
123	case "replaceOne":
124		return executeReplaceOne(ctx, op)
125	case "updateOne":
126		return executeUpdateOne(ctx, op)
127	case "updateMany":
128		return executeUpdateMany(ctx, op)
129
130	// GridFS operations
131	case "delete":
132		return executeBucketDelete(ctx, op)
133	case "download":
134		return executeBucketDownload(ctx, op)
135	case "upload":
136		return executeBucketUpload(ctx, op)
137
138	// Change Stream operations
139	case "iterateUntilDocumentOrError":
140		return executeIterateUntilDocumentOrError(ctx, op)
141	default:
142		return nil, fmt.Errorf("unrecognized entity operation %q", op.Name)
143	}
144}
145