blob: 8e45e883a900764d2e151c6a5394cb315c6499ea [file] [log] [blame]
Don Newton379ae252019-04-01 12:17:06 -04001// 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 driver
8
9import (
10 "context"
11
12 "time"
13
14 "github.com/mongodb/mongo-go-driver/bson/bsoncodec"
15 "github.com/mongodb/mongo-go-driver/mongo/options"
16 "github.com/mongodb/mongo-go-driver/mongo/writeconcern"
17 "github.com/mongodb/mongo-go-driver/x/bsonx"
18 "github.com/mongodb/mongo-go-driver/x/mongo/driver/session"
19 "github.com/mongodb/mongo-go-driver/x/mongo/driver/topology"
20 "github.com/mongodb/mongo-go-driver/x/mongo/driver/uuid"
21 "github.com/mongodb/mongo-go-driver/x/network/command"
22 "github.com/mongodb/mongo-go-driver/x/network/description"
23 "github.com/mongodb/mongo-go-driver/x/network/result"
24)
25
26// FindOneAndDelete handles the full cycle dispatch and execution of a FindOneAndDelete command against the provided
27// topology.
28func FindOneAndDelete(
29 ctx context.Context,
30 cmd command.FindOneAndDelete,
31 topo *topology.Topology,
32 selector description.ServerSelector,
33 clientID uuid.UUID,
34 pool *session.Pool,
35 retryWrite bool,
36 registry *bsoncodec.Registry,
37 opts ...*options.FindOneAndDeleteOptions,
38) (result.FindAndModify, error) {
39
40 ss, err := topo.SelectServer(ctx, selector)
41 if err != nil {
42 return result.FindAndModify{}, err
43 }
44
45 // If no explicit session and deployment supports sessions, start implicit session.
46 if cmd.Session == nil && topo.SupportsSessions() {
47 cmd.Session, err = session.NewClientSession(pool, clientID, session.Implicit)
48 if err != nil {
49 return result.FindAndModify{}, err
50 }
51 defer cmd.Session.EndSession()
52 }
53
54 do := options.MergeFindOneAndDeleteOptions(opts...)
55 if do.Collation != nil {
56 if ss.Description().WireVersion.Max < 5 {
57 return result.FindAndModify{}, ErrCollation
58 }
59 cmd.Opts = append(cmd.Opts, bsonx.Elem{"collation", bsonx.Document(do.Collation.ToDocument())})
60 }
61 if do.MaxTime != nil {
62 cmd.Opts = append(cmd.Opts, bsonx.Elem{"maxTimeMs", bsonx.Int64(int64(*do.MaxTime / time.Millisecond))})
63 }
64 if do.Projection != nil {
65 projElem, err := interfaceToElement("fields", do.Projection, registry)
66 if err != nil {
67 return result.FindAndModify{}, err
68 }
69
70 cmd.Opts = append(cmd.Opts, projElem)
71 }
72 if do.Sort != nil {
73 sortElem, err := interfaceToElement("sort", do.Sort, registry)
74 if err != nil {
75 return result.FindAndModify{}, err
76 }
77
78 cmd.Opts = append(cmd.Opts, sortElem)
79 }
80
81 // Execute in a single trip if retry writes not supported, or retry not enabled
82 if !retrySupported(topo, ss.Description(), cmd.Session, cmd.WriteConcern) || !retryWrite {
83 if cmd.Session != nil {
84 cmd.Session.RetryWrite = false // explicitly set to false to prevent encoding transaction number
85 }
86 return findOneAndDelete(ctx, cmd, ss, nil)
87 }
88
89 cmd.Session.RetryWrite = retryWrite
90 cmd.Session.IncrementTxnNumber()
91
92 res, originalErr := findOneAndDelete(ctx, cmd, ss, nil)
93
94 // Retry if appropriate
95 if cerr, ok := originalErr.(command.Error); ok && cerr.Retryable() {
96 ss, err := topo.SelectServer(ctx, selector)
97
98 // Return original error if server selection fails or new server does not support retryable writes
99 if err != nil || !retrySupported(topo, ss.Description(), cmd.Session, cmd.WriteConcern) {
100 return result.FindAndModify{}, originalErr
101 }
102
103 return findOneAndDelete(ctx, cmd, ss, cerr)
104 }
105
106 return res, originalErr
107}
108
109func findOneAndDelete(
110 ctx context.Context,
111 cmd command.FindOneAndDelete,
112 ss *topology.SelectedServer,
113 oldErr error,
114) (result.FindAndModify, error) {
115 desc := ss.Description()
116 conn, err := ss.Connection(ctx)
117 if err != nil {
118 if oldErr != nil {
119 return result.FindAndModify{}, oldErr
120 }
121 return result.FindAndModify{}, err
122 }
123
124 if !writeconcern.AckWrite(cmd.WriteConcern) {
125 go func() {
126 defer func() { _ = recover() }()
127 defer conn.Close()
128
129 _, _ = cmd.RoundTrip(ctx, desc, conn)
130 }()
131
132 return result.FindAndModify{}, command.ErrUnacknowledgedWrite
133 }
134 defer conn.Close()
135
136 return cmd.RoundTrip(ctx, desc, conn)
137}