diff --git a/event/monitoring.go b/event/monitoring.go index 2ca98969d7..8baac61190 100644 --- a/event/monitoring.go +++ b/event/monitoring.go @@ -54,6 +54,7 @@ type CommandSucceededEvent struct { type CommandFailedEvent struct { CommandFinishedEvent Failure error + Codes []int32 } // CommandMonitor represents a monitor that is triggered for different events. diff --git a/internal/integration/clam_prose_test.go b/internal/integration/clam_prose_test.go index 7dbb564280..3a4bfc1c88 100644 --- a/internal/integration/clam_prose_test.go +++ b/internal/integration/clam_prose_test.go @@ -14,9 +14,11 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/integtest" "go.mongodb.org/mongo-driver/v2/internal/logger" + "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) @@ -198,7 +200,6 @@ func clamMultiByteTruncLogs(mt *mtest.T) []truncValidator { // Insert started. validators[0] = newTruncValidator(mt, cmd, func(cmd string) error { - // Remove the suffix from the command string. cmd = cmd[:len(cmd)-len(logger.TruncationSuffix)] @@ -396,3 +397,33 @@ func TestCommandLoggingAndMonitoringProse(t *testing.T) { }) } } + +func TestCommandFailedEvent_Codes(t *testing.T) { + clientOpts := options.Client().SetRetryWrites(false) + + mtOpts := mtest.NewOptions().MinServerVersion("8.0").ClientType(mtest.Pinned).ClientOptions(clientOpts) + mt := mtest.New(t, mtOpts) + + mt.Run("Top level code", func(mt *mtest.T) { + mt.ResetClient(options.Client().SetRetryWrites(false)) + + mt.SetFailPoint(failpoint.FailPoint{ + ConfigureFailPoint: "failCommand", + Mode: failpoint.Mode{ + Times: 1, + }, + Data: failpoint.Data{ + FailCommands: []string{"insert"}, + ErrorCode: 1, + }, + }) + + _, err := mt.Coll.InsertOne(context.Background(), struct{ X int }{X: 1}) + require.Error(mt, err, "expected InsertOne error, got nil") + + failedEvent := mt.GetFailedEvent() + + require.NotNil(mt, failedEvent, "expected CommandFailedEvent, got nil") + require.Equal(mt, []int32{1}, failedEvent.Codes, "expected Codes to be [1], got %+v", failedEvent.Codes) + }) +} diff --git a/mongo/errors.go b/mongo/errors.go index 234445ab86..b360d2214f 100644 --- a/mongo/errors.go +++ b/mongo/errors.go @@ -364,10 +364,12 @@ func hasErrorCode(srvErr ServerError, code int) bool { return false } -var _ ServerError = CommandError{} -var _ ServerError = WriteError{} -var _ ServerError = WriteException{} -var _ ServerError = BulkWriteException{} +var ( + _ ServerError = CommandError{} + _ ServerError = WriteError{} + _ ServerError = WriteException{} + _ ServerError = BulkWriteException{} +) var _ error = ClientBulkWriteException{} diff --git a/x/mongo/driver/errors.go b/x/mongo/driver/errors.go index 9191b26d25..656f3c6d4e 100644 --- a/x/mongo/driver/errors.go +++ b/x/mongo/driver/errors.go @@ -566,3 +566,21 @@ func ExtractErrorFromServerResponse(doc bsoncore.Document) error { return nil } + +// errorCodes extracts error codes from a driver.Error representing an ok:0 +// command failure. Since ok:0 indicates the command failed before processing +// any operations, only a single error code is returned. Returns nil if the +// error is not a driver.Error (e.g., network errors, WriteCommandError for +// ok:1 responses). +// +// This function may be updated in the future to extract multiple error codes in +// an ok:1 scenario. In such cases, there can be multiple error codes via +// WriteErrors which represent individual operation failures within the context +// of a write command. +func errorCodes(err error) []int32 { + if driversErr, ok := err.(Error); ok { + return []int32{driversErr.Code} + } + + return nil +} diff --git a/x/mongo/driver/operation.go b/x/mongo/driver/operation.go index 3e720eba63..6834fd5afb 100644 --- a/x/mongo/driver/operation.go +++ b/x/mongo/driver/operation.go @@ -747,7 +747,6 @@ func (op Operation) Execute(ctx context.Context) error { var moreToCome bool var startedInfo startedInformation *wm, moreToCome, startedInfo, err = op.createWireMessage(ctx, maxTimeMS, (*wm)[:0], desc, conn, requestID) - if err != nil { return err } @@ -2231,6 +2230,7 @@ func (op Operation) publishFinishedEvent(ctx context.Context, info finishedInfor failedEvent := &event.CommandFailedEvent{ Failure: info.cmdErr, + Codes: errorCodes(info.cmdErr), CommandFinishedEvent: finished, } op.CommandMonitor.Failed(ctx, failedEvent)