Inconsistent API for updates and aggregation

I’m using the go driver. Some of these issues are likely due to the go driver, but I believe the bulk is due to the API itself.

Here’s some inconsistencies:

type MyStruct struct {
   Field1 map[string]struct{}
   Field2 []string
   Index int
   Field4 SomeOtherStruct
}


// this works, however using $inc instead of set does not work. That's ok, I can deal.
collection.UpdateOne(ctx, bson.M{"id": id, "version": version}, mongo.Pipeline{
		{{Key: "$set", Value: bson.M{"index": 3}}},
		{{Key: "$addFields",
			Value: bson.M{
				"field2": bson.M{
					"$arrayElemAt": []interface{}{"$field2", "$index"},
				},
			},
		}},
	})

// A little frustrating I can't use $index here, but again I'll live.
collection.UpdateOne(ctx, bson.M{"id": id}, bson.D{
	{Key: "$set", Value: MyStruct{}}, {Key: "$push", Value: bson.M{
		"field2": bson.M{
			"$each":     myStringArray,
			"$position": "$index", // can't lookup the index here
		},
	}})

// But wait, I can't use the struct in a pipeline?!? (Hint: it complains about empty fields). Ok, starting to reconsider using mongo now...
collection.UpdateOne(ctx, bson.M{"id": id, "version": version}, mongo.Pipeline{
		{{Key: "$set", MyStruct{}}}, // doesn't like the empty fields here.
		{{Key: "$addFields",
			Value: bson.M{
				"field2": bson.M{
					"$arrayElemAt": []interface{}{"$field2", "$index"},
				},
			},
		}},
	})

And there’s no fix! I can’t use addFields, or set with a lookup on arrayElemAt outside of the pipeline, and I can’t use set with the struct that may have empty fields in a pipeline.

Doing 2 queries to accomplish this is not even the frustrating part. The frustrating part is the lack of documentation around what is allowed to be strung together under what circumstances. Maybe I’ll understand it more with time. But API design is important, and it’s clear there is room for improvement here

Hello @Sean_Teeling, welcome to the MongoDB Community forum! I have tried to address your issues (three of them).

Issue 1:

// this works, however using $inc instead of set does not work. That's ok, I can deal.
collection.UpdateOne(ctx, bson.M{"id": id, "version": version}, mongo.Pipeline{
		{{Key: "$set", Value: bson.M{"index": 3}}},
		{{Key: "$addFields",
			Value: bson.M{
				"field2": bson.M{
					"$arrayElemAt": []interface{}{"$field2", "$index"},
				},
			},
		}},
	})

The $inc is a MongoDB Query Language’s (MQL) update operator. The MQL operators do not work with Aggregation Pipeline’s $addFields and $set stages. In the above update operation, you are using a pipeline to perform the updates, so use the pipeline operators.

Why MQL and Pipeline for update operations?

Most of the general purpose updates can be handled with the MQL operators, and for more complex operations the pipeline is the tool to go with as aggregation operators are a larger and versatile set of tools.

Now consider your code: {{Key: "$set", Value: bson.M{"index": 3}}}
Since, you want to increment the value by n, you can also write it as (in native code):
{ $addFields: { $add: [ "$index", n ] } }

Note: The $set and $addFields are the same - when used within a pipeline.

Reference:



Issue 2:

// A little frustrating I can't use $index here, but again I'll live.
collection.UpdateOne(ctx, bson.M{"id": id}, bson.D{
	{Key: "$set", Value: MyStruct{}}, {Key: "$push", Value: bson.M{
		"field2": bson.M{
			"$each":     myStringArray,
			"$position": "$index", // can't lookup the index here
		},
	}})

This update operation is an MQL update operation. The $push you are using is a MQL update operator used for working with array fields.

"$position": "$index", // can't lookup the index here

Yes, that error is correct and is as expected. With MQL update operators you cannot assign the document field values to other document fields (i.e., use them on the right-hand side of an update operation, as you are trying). This is where you should be using the pipeline, which allows assigning same document field values.



Issue 3:

// But wait, I can't use the struct in a pipeline?!? (Hint: it complains about empty fields). Ok, starting to reconsider using mongo now...
collection.UpdateOne(ctx, bson.M{"id": id, "version": version}, mongo.Pipeline{
		{{Key: "$set", MyStruct{}}}, // doesn't like the empty fields here.
		{{Key: "$addFields",
			Value: bson.M{
				"field2": bson.M{
					"$arrayElemAt": []interface{}{"$field2", "$index"},
				},
			},
		}},
	})

About your code: {{Key: "$set", MyStruct{}}}, // doesn't like the empty fields here.

You can use either of the following two - both worked for me:

bson.D{{"$set", MyStruct{}}}
bson.D{{Key: "$set", Value: MyStruct{}}}