11package mgo
22
3+ import (
4+ "fmt"
5+ "reflect"
6+ "sync"
7+
8+ "gopkg.in/mgo.v2/bson"
9+ )
10+
311type ChangeStream struct {
412 iter * Iter
513 options ChangeStreamOptions
614 pipeline interface {}
15+ resumeToken * bson.Raw
16+ collection * Collection
717 readPreference * ReadPreference
18+ err error
19+ m sync.Mutex
820}
921
1022type ChangeStreamOptions struct {
@@ -27,6 +39,74 @@ type ChangeStreamOptions struct {
2739 Collation * Collation
2840}
2941
42+ // Next retrieves the next document from the change stream, blocking if necessary.
43+ // Next returns true if a document was successfully unmarshalled into result,
44+ // and false if an error occured. When Next returns false, the Err method should
45+ // be called to check what error occurred during iteration.
46+ //
47+ // For example:
48+ //
49+ // pipeline := []bson.M{}
50+ //
51+ // changeStream := collection.Watch(pipeline, ChangeStreamOptions{})
52+ // for changeStream.Next(&changeDoc) {
53+ // fmt.Printf("Change: %v\n", changeDoc)
54+ // }
55+ //
56+ // if err := changeStream.Close(); err != nil {
57+ // return err
58+ // }
59+ //
60+ // If the pipeline used removes the _id field from the result, Next will error
61+ // because the _id field is needed to resume iteration when an error occurs.
62+ //
63+ func (changeStream * ChangeStream ) Next (result interface {}) bool {
64+ // the err field is being constantly overwritten and we don't want the user to
65+ // attempt to read it at this point so we lock.
66+ changeStream .m .Lock ()
67+
68+ defer changeStream .m .Unlock ()
69+
70+ // if we are in a state of error, then don't continue.
71+ if changeStream .err != nil {
72+ return false
73+ }
74+
75+ var err error
76+
77+ // attempt to fetch the change stream result.
78+ err = changeStream .fetchResultSet (result )
79+ if err == nil {
80+ return true
81+ }
82+
83+ // check if the error is resumable
84+ if ! isResumableError (err ) {
85+ // error is not resumable, give up and return it to the user.
86+ changeStream .err = err
87+ return false
88+ }
89+
90+ // try to resume.
91+ err = changeStream .resume ()
92+ if err != nil {
93+ // we've not been able to successfully resume and should only try once,
94+ // so we give up.
95+ changeStream .err = err
96+ return false
97+ }
98+
99+ // we've successfully resumed the changestream.
100+ // try to fetch the next result.
101+ err = changeStream .fetchResultSet (result )
102+ if err != nil {
103+ changeStream .err = err
104+ return false
105+ }
106+
107+ return true
108+ }
109+
30110func constructChangeStreamPipeline (pipeline interface {},
31111 options ChangeStreamOptions ) interface {} {
32112 pipelinev := reflect .ValueOf (pipeline )
@@ -38,21 +118,21 @@ func constructChangeStreamPipeline(pipeline interface{},
38118
39119 // construct the options to be used by the change notification
40120 // pipeline stage.
41- changeNotificationStageOptions := bson.M {}
121+ changeStreamStageOptions := bson.M {}
42122
43123 if options .FullDocument != "" {
44- changeNotificationStageOptions ["fullDocument" ] = options .FullDocument
124+ changeStreamStageOptions ["fullDocument" ] = options .FullDocument
45125 }
46126 if options .ResumeAfter != nil {
47- changeNotificationStageOptions ["resumeAfter" ] = options .ResumeAfter
127+ changeStreamStageOptions ["resumeAfter" ] = options .ResumeAfter
48128 }
49- changeNotificationStage := bson.M {"$changeNotification " : changeNotificationStageOptions }
129+ changeStreamStage := bson.M {"$changeStream " : changeStreamStageOptions }
50130
51131 pipeOfInterfaces := make ([]interface {}, pipelinev .Len ()+ 1 )
52132
53133 // insert the change notification pipeline stage at the beginning of the
54134 // aggregation.
55- pipeOfInterfaces [0 ] = changeNotificationStage
135+ pipeOfInterfaces [0 ] = changeStreamStage
56136
57137 // convert the passed in slice to a slice of interfaces.
58138 for i := 0 ; i < pipelinev .Len (); i ++ {
@@ -61,3 +141,102 @@ func constructChangeStreamPipeline(pipeline interface{},
61141 var pipelineAsInterface interface {} = pipeOfInterfaces
62142 return pipelineAsInterface
63143}
144+
145+ func (changeStream * ChangeStream ) resume () error {
146+ // copy the information for the new socket.
147+
148+ // Copy() destroys the sockets currently associated with this session
149+ // so future uses will acquire a new socket against the newly selected DB.
150+ newSession := changeStream .iter .session .Copy ()
151+
152+ // fetch the cursor from the iterator and use it to run a killCursors
153+ // on the connection.
154+ cursorId := changeStream .iter .op .cursorId
155+ err := runKillCursorsOnSession (newSession , cursorId )
156+ if err != nil {
157+ return err
158+ }
159+
160+ // change out the old connection to the database with the new connection.
161+ changeStream .collection .Database .Session = newSession
162+
163+ // make a new pipeline containing the resume token.
164+ changeStreamPipeline := constructChangeStreamPipeline (changeStream .pipeline , changeStream .options )
165+
166+ // generate the new iterator with the new connection.
167+ newPipe := changeStream .collection .Pipe (changeStreamPipeline )
168+ changeStream .iter = newPipe .Iter ()
169+ changeStream .iter .isChangeStream = true
170+
171+ return nil
172+ }
173+
174+ // fetchResumeToken unmarshals the _id field from the document, setting an error
175+ // on the changeStream if it is unable to.
176+ func (changeStream * ChangeStream ) fetchResumeToken (rawResult * bson.Raw ) error {
177+ changeStreamResult := struct {
178+ ResumeToken * bson.Raw `bson:"_id,omitempty"`
179+ }{}
180+
181+ err := rawResult .Unmarshal (& changeStreamResult )
182+ if err != nil {
183+ return err
184+ }
185+
186+ if changeStreamResult .ResumeToken == nil {
187+ return fmt .Errorf ("resume token missing from result" )
188+ }
189+
190+ changeStream .resumeToken = changeStreamResult .ResumeToken
191+ return nil
192+ }
193+
194+ func (changeStream * ChangeStream ) fetchResultSet (result interface {}) error {
195+ rawResult := bson.Raw {}
196+
197+ // fetch the next set of documents from the cursor.
198+ gotNext := changeStream .iter .Next (& rawResult )
199+
200+ err := changeStream .iter .Err ()
201+ if err != nil {
202+ return err
203+ }
204+
205+ if ! gotNext && err == nil {
206+ // If the iter.Err() method returns nil despite us not getting a next batch,
207+ // it is becuase iter.Err() silences this case.
208+ return ErrNotFound
209+ }
210+
211+ // grab the resumeToken from the results
212+ if err := changeStream .fetchResumeToken (& rawResult ); err != nil {
213+ return err
214+ }
215+
216+ // put the raw results into the data structure the user provided.
217+ if err := rawResult .Unmarshal (result ); err != nil {
218+ return err
219+ }
220+ return nil
221+ }
222+
223+ func isResumableError (err error ) bool {
224+ _ , isQueryError := err .(* QueryError )
225+ // if it is not a database error OR it is a database error,
226+ // but the error is a notMaster error
227+ return ! isQueryError || isNotMasterError (err )
228+ }
229+
230+ func runKillCursorsOnSession (session * Session , cursorId int64 ) error {
231+ socket , err := session .acquireSocket (true )
232+ if err != nil {
233+ return err
234+ }
235+ err = socket .Query (& killCursorsOp {[]int64 {cursorId }})
236+ if err != nil {
237+ return err
238+ }
239+ socket .Release ()
240+
241+ return nil
242+ }
0 commit comments