Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return command error from findAndModify #2646

Merged
merged 17 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 169 additions & 4 deletions integration/findandmodify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package integration

import (
"math"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -57,8 +58,11 @@ func TestFindAndModifyEmptyCollectionName(t *testing.T) {
func TestFindAndModifyErrors(t *testing.T) {
t.Parallel()

for name, tc := range map[string]struct {
command bson.D
for name, tc := range map[string]struct { //nolint:vet // it is used for test only
command bson.D
findAndModify string // optional, defaults to "findAndModify"
provider shareddata.Provider // optional, default uses shareddata.ArrayDocuments

err *mongo.CommandError
altMessage string
}{
Expand Down Expand Up @@ -122,13 +126,174 @@ func TestFindAndModifyErrors(t *testing.T) {
},
altMessage: "BSON field 'findAndModify.upsert' is the wrong type 'string', expected type 'bool'",
},
"LowerCaseCommand": {
// go driver sends `findAndModify` in camel case, but
// js driver sends `findandmodify` in lower case.
rumyantseva marked this conversation as resolved.
Show resolved Hide resolved
findAndModify: "findandmodify",
command: bson.D{
{"update", bson.D{{"$set", bson.D{{"_id", "non-existent"}}}}},
},
err: &mongo.CommandError{
Code: 66,
Name: "ImmutableField",
Message: "Plan executor error during findAndModify :: caused by :: Performing an update on the path '_id' would modify the immutable field '_id'",
},
altMessage: "Performing an update on the path '_id' would modify the immutable field '_id'",
},
"SetUnsuitableValue": {
command: bson.D{
{"query", bson.D{{"_id", "array-documents-nested"}}},
{"update", bson.D{{"$set", bson.D{{"v.foo", "foo"}}}}},
},
err: &mongo.CommandError{
Code: 28,
Name: "PathNotViable",
Message: "Plan executor error during findAndModify :: caused by :: Cannot create field 'foo' " +
"in element {v: [ { foo: [ { bar: \"hello\" }, { bar: \"world\" } ] } ]}",
},
altMessage: "Cannot create field 'foo' in element " +
"{v: [ { foo: [ { bar: \"hello\" }, { bar: \"world\" } ] } ]}",
},
"RenameEmptyFieldName": {
command: bson.D{
{"query", bson.D{{"_id", "array-documents-nested"}}},
{"update", bson.D{{"$rename", bson.D{{"", "v"}}}}},
},
err: &mongo.CommandError{
Code: 56,
Name: "EmptyFieldName",
Message: "An empty update path is not valid.",
},
},
"RenameEmptyPath": {
command: bson.D{
{"query", bson.D{{"_id", "array-documents-nested"}}},
{"update", bson.D{{"$rename", bson.D{{"v.", "v"}}}}},
},
err: &mongo.CommandError{
Code: 56,
Name: "EmptyFieldName",
Message: "The update path 'v.' contains an empty field name, which is not allowed.",
},
},
"RenameArrayInvalidIndex": {
command: bson.D{
{"query", bson.D{{"_id", "array-documents-nested"}}},
{"update", bson.D{{"$rename", bson.D{{"v.-1", "f"}}}}},
},
err: &mongo.CommandError{
Code: 28,
Name: "PathNotViable",
Message: "Plan executor error during findAndModify :: caused by :: " +
"cannot use the part (v of v.-1) to traverse the element " +
"({v: [ { foo: [ { bar: \"hello\" }, { bar: \"world\" } ] } ]})",
},
altMessage: "cannot use path 'v.-1' to traverse the document",
},
"RenameUnsuitableValue": {
command: bson.D{
{"query", bson.D{{"_id", "array-documents-nested"}}},
{"update", bson.D{{"$rename", bson.D{{"v.0.foo.0.bar.z", "f"}}}}},
},
err: &mongo.CommandError{
Code: 28,
Name: "PathNotViable",
Message: "Plan executor error during findAndModify :: caused by :: " +
"cannot use the part (bar of v.0.foo.0.bar.z) to traverse the element ({bar: \"hello\"})",
},
altMessage: "types.getByPath: can't access string by path \"z\"",
},
"IncTypeMismatch": {
command: bson.D{
{"query", bson.D{{"_id", "array-documents-nested"}}},
{"update", bson.D{{"$inc", bson.D{{"v", "string"}}}}},
},
err: &mongo.CommandError{
Code: 14,
Name: "TypeMismatch",
Message: "Cannot increment with non-numeric argument: {v: \"string\"}",
},
},
"IncUnsuitableValue": {
command: bson.D{
{"query", bson.D{{"_id", "array-documents-nested"}}},
{"update", bson.D{{"$inc", bson.D{{"v.foo", 1}}}}},
},
err: &mongo.CommandError{
Code: 28,
Name: "PathNotViable",
Message: "Plan executor error during findAndModify :: caused by :: " +
"Cannot create field 'foo' in element " +
"{v: [ { foo: [ { bar: \"hello\" }, { bar: \"world\" } ] } ]}",
},
altMessage: "Cannot create field 'foo' in element " +
"{v: [ { foo: [ { bar: \"hello\" }, { bar: \"world\" } ] } ]}",
},
"IncNonNumeric": {
command: bson.D{
{"query", bson.D{{"_id", "array-documents-nested"}}},
{"update", bson.D{{"$inc", bson.D{{"v.0.foo.0.bar", 1}}}}},
},
err: &mongo.CommandError{
Code: 14,
Name: "TypeMismatch",
Message: "Plan executor error during findAndModify :: caused by :: " +
"Cannot apply $inc to a value of non-numeric type. " +
"{_id: \"array-documents-nested\"} has the field 'bar' of non-numeric type string",
},
altMessage: "Cannot apply $inc to a value of non-numeric type. " +
"{_id: \"array-documents-nested\"} has the field 'bar' of non-numeric type string",
},
"IncInt64BadValue": {
command: bson.D{
{"query", bson.D{{"_id", "int64-max"}}},
{"update", bson.D{{"$inc", bson.D{{"v", math.MaxInt64}}}}},
},
err: &mongo.CommandError{
Code: 2,
Name: "BadValue",
Message: "Plan executor error during findAndModify :: caused by :: " +
"Failed to apply $inc operations to current value " +
"((NumberLong)9223372036854775807) for document {_id: \"int64-max\"}",
},
provider: shareddata.Int64s,
altMessage: "Failed to apply $inc operations to current value " +
"((NumberLong)9223372036854775807) for document {_id: \"int64-max\"}",
},
"IncInt32BadValue": {
command: bson.D{
{"query", bson.D{{"_id", "int32"}}},
{"update", bson.D{{"$inc", bson.D{{"v", math.MaxInt64}}}}},
},
err: &mongo.CommandError{
Code: 2,
Name: "BadValue",
Message: "Plan executor error during findAndModify :: caused by :: " +
"Failed to apply $inc operations to current value " +
"((NumberInt)42) for document {_id: \"int32\"}",
},
provider: shareddata.Int32s,
altMessage: "Failed to apply $inc operations to current value " +
"((NumberInt)42) for document {_id: \"int32\"}",
},
} {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
t.Parallel()
ctx, collection := setup.Setup(t, shareddata.DocumentsStrings)

command := bson.D{{"findAndModify", collection.Name()}}
provider := tc.provider
if provider == nil {
provider = shareddata.ArrayDocuments
}

ctx, collection := setup.Setup(t, provider)

findAndModify := "findAndModify"
if tc.findAndModify != "" {
findAndModify = tc.findAndModify
}

command := bson.D{{findAndModify, collection.Name()}}
command = append(command, tc.command...)
if command.Map()["sort"] == nil {
command = append(command, bson.D{{"sort", bson.D{{"_id", 1}}}}...)
Expand Down
121 changes: 121 additions & 0 deletions integration/update_field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package integration

import (
"math"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -81,3 +82,123 @@ func TestUpdateFieldSet(t *testing.T) {
})
}
}

func TestUpdateFieldErrors(t *testing.T) {
t.Parallel()

for name, tc := range map[string]struct { //nolint:vet // it is used for test only
id string
update bson.D
provider shareddata.Provider // optional, default uses shareddata.ArrayDocuments

err *mongo.WriteError
altMessage string
}{
"SetUnsuitableValue": {
id: "array-documents-nested",
update: bson.D{{"$rename", bson.D{{"v.foo", "foo"}}}},
err: &mongo.WriteError{
Code: 28,
Message: "cannot use the part (v of v.foo) to traverse the element " +
"({v: [ { foo: [ { bar: \"hello\" }, { bar: \"world\" } ] } ]})",
},
altMessage: "cannot use path 'v.foo' to traverse the document",
},
"RenameEmptyFieldName": {
id: "array-documents-nested",
update: bson.D{{"$rename", bson.D{{"", "v"}}}},
err: &mongo.WriteError{
Code: 56,
Message: "An empty update path is not valid.",
},
},
"RenameEmptyPath": {
id: "array-documents-nested",
update: bson.D{{"$rename", bson.D{{"v.", "v"}}}},
err: &mongo.WriteError{
Code: 56,
Message: "The update path 'v.' contains an empty field name, which is not allowed.",
},
},
"RenameArrayInvalidIndex": {
id: "array-documents-nested",
update: bson.D{{"$rename", bson.D{{"v.-1", "f"}}}},
err: &mongo.WriteError{
Code: 28,
Message: "cannot use the part (v of v.-1) to traverse the element " +
"({v: [ { foo: [ { bar: \"hello\" }, { bar: \"world\" } ] } ]})",
},
altMessage: "cannot use path 'v.-1' to traverse the document",
},
"RenameUnsuitableValue": {
id: "array-documents-nested",
update: bson.D{{"$rename", bson.D{{"v.0.foo.0.bar.z", "f"}}}},
err: &mongo.WriteError{
Code: 28,
Message: "cannot use the part (bar of v.0.foo.0.bar.z) to traverse the element ({bar: \"hello\"})",
},
altMessage: "types.getByPath: can't access string by path \"z\"",
},
"IncTypeMismatch": {
id: "array-documents-nested",
update: bson.D{{"$inc", bson.D{{"v", "string"}}}},
err: &mongo.WriteError{
Code: 14,
Message: "Cannot increment with non-numeric argument: {v: \"string\"}",
},
},
"IncUnsuitableValue": {
id: "array-documents-nested",
update: bson.D{{"$inc", bson.D{{"v.foo", 1}}}},
err: &mongo.WriteError{
Code: 28,
Message: "Cannot create field 'foo' in element " +
"{v: [ { foo: [ { bar: \"hello\" }, { bar: \"world\" } ] } ]}",
},
},
"IncNonNumeric": {
id: "array-documents-nested",
update: bson.D{{"$inc", bson.D{{"v.0.foo.0.bar", 1}}}},
err: &mongo.WriteError{
Code: 14,
Message: "Cannot apply $inc to a value of non-numeric type. " +
"{_id: \"array-documents-nested\"} has the field 'bar' of non-numeric type string",
},
},
"IncInt64BadValue": {
id: "int64-max",
update: bson.D{{"$inc", bson.D{{"v", math.MaxInt64}}}},
err: &mongo.WriteError{
Code: 2,
Message: "Failed to apply $inc operations to current value " +
"((NumberLong)9223372036854775807) for document {_id: \"int64-max\"}",
},
provider: shareddata.Int64s,
},
"IncInt32BadValue": {
id: "int32",
update: bson.D{{"$inc", bson.D{{"v", math.MaxInt64}}}},
err: &mongo.WriteError{
Code: 2,
Message: "Failed to apply $inc operations to current value " +
"((NumberInt)42) for document {_id: \"int32\"}",
},
provider: shareddata.Int32s,
},
} {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
t.Parallel()

provider := tc.provider
if provider == nil {
provider = shareddata.ArrayDocuments
}

ctx, collection := setup.Setup(t, provider)

_, err := collection.UpdateOne(ctx, bson.D{{"_id", tc.id}}, tc.update)
AssertEqualAltWriteError(t, *tc.err, tc.altMessage, err)
})
}
}
4 changes: 2 additions & 2 deletions internal/handlers/common/findandmodify.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func prepareDocumentForInsert(params *FindAndModifyParams) (*types.Document, err
insert := must.NotFail(types.NewDocument())

if params.HasUpdateOperators {
if _, err := UpdateDocument(insert, params.Update); err != nil {
if _, err := UpdateDocument("findAndModify", insert, params.Update); err != nil {
return nil, err
}
} else {
Expand All @@ -224,7 +224,7 @@ func prepareDocumentForUpdate(docs []*types.Document, params *FindAndModifyParam
update := docs[0].DeepCopy()

if params.HasUpdateOperators {
if _, err := UpdateDocument(update, params.Update); err != nil {
if _, err := UpdateDocument("findAndModify", update, params.Update); err != nil {
return nil, err
}

Expand Down
Loading