Merge lp:~rogpeppe/mgo/thread-safe-newobject into lp:mgo
- thread-safe-newobject
- Merge into placeholder
Status: | Work in progress |
---|---|
Proposed branch: | lp:~rogpeppe/mgo/thread-safe-newobject |
Merge into: | lp:mgo |
Diff against target: |
17597 lines (+17399/-0) (has conflicts) 39 files modified
.bzrignore (+2/-0) LICENSE (+25/-0) Makefile (+5/-0) auth.go (+276/-0) auth_test.go (+560/-0) bson/LICENSE (+25/-0) bson/bson.go (+639/-0) bson/bson_test.go (+1369/-0) bson/decode.go (+734/-0) bson/encode.go (+430/-0) cluster.go (+504/-0) cluster_test.go (+1108/-0) doc.go (+30/-0) export_test.go (+10/-0) gridfs.go (+716/-0) gridfs_test.go (+607/-0) log.go (+89/-0) queue.go (+91/-0) queue_test.go (+104/-0) server.go (+332/-0) session.go (+3075/-0) session_test.go (+2978/-0) socket.go (+550/-0) stats.go (+137/-0) suite_test.go (+170/-0) testdb/dropall.js (+31/-0) testdb/init.js (+86/-0) testdb/setup.sh (+52/-0) testdb/supervisord.conf (+49/-0) testdb/wait.js (+51/-0) txn/chaos.go (+68/-0) txn/debug.go (+99/-0) txn/flusher.go (+985/-0) txn/mgo_test.go (+101/-0) txn/sim_test.go (+389/-0) txn/tarjan.go (+96/-0) txn/tarjan_test.go (+44/-0) txn/txn.go (+444/-0) txn/txn_test.go (+338/-0) Conflict adding file bson. Moved existing file to bson.moved. |
To merge this branch: | bzr merge lp:~rogpeppe/mgo/thread-safe-newobject |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Pending | ||
Review via email: mp+156782@code.launchpad.net |
Commit message
Description of the change
bson: make NewObjectId thread safe
Also use an array rather than a slice to
remove a few unnecessary bounds checks.
Unmerged revisions
- 196. By Roger Peppe
-
bson: make NewObjectId thread safe
- 195. By Gustavo Niemeyer
-
Other minor test fixes for 2.4.
- 194. By Gustavo Niemeyer
-
auth_test.go: fix expected auth error messages for 2.4
- 193. By Gustavo Niemeyer
-
supervisord.conf: drop startsecs for mongos
- 192. By Gustavo Niemeyer
-
Test direct connection to a replicaset server in unknown membership state.
- 191. By Gustavo Niemeyer
-
Allow direct connection to replica set member in unknown state.
- 190. By Gustavo Niemeyer
-
Fixed omitempty on float values, as reported by Otto Bretz.
- 189. By Gustavo Niemeyer
-
New bson.IsObjectIdHex function.
- 188. By Gustavo Niemeyer
-
In preparation for 2.4, which is coming with multiple new index types,
change the format of 2D indexes from "@foo" to "$2d:foo". Preserve
parsing of the old style for compatibility. - 187. By Gustavo Niemeyer
-
Fix DialWithInfo so it dials to the standard MongoDB port if the address
doesn't inform one. Thanks to Mark Severson for reporting the issue.
Preview Diff
1 | === added file '.bzrignore' |
2 | --- .bzrignore 1970-01-01 00:00:00 +0000 |
3 | +++ .bzrignore 2013-04-03 09:16:27 +0000 |
4 | @@ -0,0 +1,2 @@ |
5 | +_* |
6 | +[856].out |
7 | |
8 | === added file 'LICENSE' |
9 | --- LICENSE 1970-01-01 00:00:00 +0000 |
10 | +++ LICENSE 2013-04-03 09:16:27 +0000 |
11 | @@ -0,0 +1,25 @@ |
12 | +mgo - MongoDB driver for Go |
13 | + |
14 | +Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
15 | + |
16 | +All rights reserved. |
17 | + |
18 | +Redistribution and use in source and binary forms, with or without |
19 | +modification, are permitted provided that the following conditions are met: |
20 | + |
21 | +1. Redistributions of source code must retain the above copyright notice, this |
22 | + list of conditions and the following disclaimer. |
23 | +2. Redistributions in binary form must reproduce the above copyright notice, |
24 | + this list of conditions and the following disclaimer in the documentation |
25 | + and/or other materials provided with the distribution. |
26 | + |
27 | +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
28 | +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
29 | +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
30 | +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
31 | +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
32 | +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
33 | +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
34 | +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
35 | +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
36 | +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
37 | |
38 | === added file 'Makefile' |
39 | --- Makefile 1970-01-01 00:00:00 +0000 |
40 | +++ Makefile 2013-04-03 09:16:27 +0000 |
41 | @@ -0,0 +1,5 @@ |
42 | +startdb: |
43 | + @testdb/setup.sh start |
44 | + |
45 | +stopdb: |
46 | + @testdb/setup.sh stop |
47 | |
48 | === added file 'auth.go' |
49 | --- auth.go 1970-01-01 00:00:00 +0000 |
50 | +++ auth.go 2013-04-03 09:16:27 +0000 |
51 | @@ -0,0 +1,276 @@ |
52 | +// mgo - MongoDB driver for Go |
53 | +// |
54 | +// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
55 | +// |
56 | +// All rights reserved. |
57 | +// |
58 | +// Redistribution and use in source and binary forms, with or without |
59 | +// modification, are permitted provided that the following conditions are met: |
60 | +// |
61 | +// 1. Redistributions of source code must retain the above copyright notice, this |
62 | +// list of conditions and the following disclaimer. |
63 | +// 2. Redistributions in binary form must reproduce the above copyright notice, |
64 | +// this list of conditions and the following disclaimer in the documentation |
65 | +// and/or other materials provided with the distribution. |
66 | +// |
67 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
68 | +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
69 | +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
70 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
71 | +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
72 | +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
73 | +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
74 | +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
75 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
76 | +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
77 | + |
78 | +package mgo |
79 | + |
80 | +import ( |
81 | + "crypto/md5" |
82 | + "encoding/hex" |
83 | + "errors" |
84 | + "fmt" |
85 | + "labix.org/v2/mgo/bson" |
86 | + "sync" |
87 | +) |
88 | + |
89 | +type authInfo struct { |
90 | + db, user, pass string |
91 | +} |
92 | + |
93 | +type authCmd struct { |
94 | + Authenticate int |
95 | + Nonce, User, Key string |
96 | +} |
97 | + |
98 | +type authResult struct { |
99 | + ErrMsg string |
100 | + Ok bool |
101 | +} |
102 | + |
103 | +type getNonceCmd struct { |
104 | + GetNonce int |
105 | +} |
106 | + |
107 | +type getNonceResult struct { |
108 | + Nonce string |
109 | + Err string "$err" |
110 | + Code int |
111 | +} |
112 | + |
113 | +type logoutCmd struct { |
114 | + Logout int |
115 | +} |
116 | + |
117 | +func (socket *mongoSocket) getNonce() (nonce string, err error) { |
118 | + socket.Lock() |
119 | + for socket.cachedNonce == "" && socket.dead == nil { |
120 | + debugf("Socket %p to %s: waiting for nonce", socket, socket.addr) |
121 | + socket.gotNonce.Wait() |
122 | + } |
123 | + if socket.cachedNonce == "mongos" { |
124 | + socket.Unlock() |
125 | + return "", errors.New("Can't authenticate with mongos; see http://j.mp/mongos-auth") |
126 | + } |
127 | + debugf("Socket %p to %s: got nonce", socket, socket.addr) |
128 | + nonce, err = socket.cachedNonce, socket.dead |
129 | + socket.cachedNonce = "" |
130 | + socket.Unlock() |
131 | + if err != nil { |
132 | + nonce = "" |
133 | + } |
134 | + return |
135 | +} |
136 | + |
137 | +func (socket *mongoSocket) resetNonce() { |
138 | + debugf("Socket %p to %s: requesting a new nonce", socket, socket.addr) |
139 | + op := &queryOp{} |
140 | + op.query = &getNonceCmd{GetNonce: 1} |
141 | + op.collection = "admin.$cmd" |
142 | + op.limit = -1 |
143 | + op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) { |
144 | + if err != nil { |
145 | + socket.kill(errors.New("getNonce: " + err.Error()), true) |
146 | + return |
147 | + } |
148 | + result := &getNonceResult{} |
149 | + err = bson.Unmarshal(docData, &result) |
150 | + if err != nil { |
151 | + socket.kill(errors.New("Failed to unmarshal nonce: " + err.Error()), true) |
152 | + return |
153 | + } |
154 | + debugf("Socket %p to %s: nonce unmarshalled: %#v", socket, socket.addr, result) |
155 | + if result.Code == 13390 { |
156 | + // mongos doesn't yet support auth (see http://j.mp/mongos-auth) |
157 | + result.Nonce = "mongos" |
158 | + } else if result.Nonce == "" { |
159 | + var msg string |
160 | + if result.Err != "" { |
161 | + msg = fmt.Sprintf("Got an empty nonce: %s (%d)", result.Err, result.Code) |
162 | + } else { |
163 | + msg = "Got an empty nonce" |
164 | + } |
165 | + socket.kill(errors.New(msg), true) |
166 | + return |
167 | + } |
168 | + socket.Lock() |
169 | + if socket.cachedNonce != "" { |
170 | + socket.Unlock() |
171 | + panic("resetNonce: nonce already cached") |
172 | + } |
173 | + socket.cachedNonce = result.Nonce |
174 | + socket.gotNonce.Signal() |
175 | + socket.Unlock() |
176 | + } |
177 | + err := socket.Query(op) |
178 | + if err != nil { |
179 | + socket.kill(errors.New("resetNonce: " + err.Error()), true) |
180 | + } |
181 | +} |
182 | + |
183 | +func (socket *mongoSocket) Login(db string, user string, pass string) error { |
184 | + socket.Lock() |
185 | + for _, a := range socket.auth { |
186 | + if a.db == db && a.user == user && a.pass == pass { |
187 | + debugf("Socket %p to %s: login: db=%q user=%q (already logged in)", socket, socket.addr, db, user) |
188 | + socket.Unlock() |
189 | + return nil |
190 | + } |
191 | + } |
192 | + if auth, found := socket.dropLogout(db, user, pass); found { |
193 | + debugf("Socket %p to %s: login: db=%q user=%q (cached)", socket, socket.addr, db, user) |
194 | + socket.auth = append(socket.auth, auth) |
195 | + socket.Unlock() |
196 | + return nil |
197 | + } |
198 | + socket.Unlock() |
199 | + |
200 | + debugf("Socket %p to %s: login: db=%q user=%q", socket, socket.addr, db, user) |
201 | + |
202 | + // Note that this only works properly because this function is |
203 | + // synchronous, which means the nonce won't get reset while we're |
204 | + // using it and any other login requests will block waiting for a |
205 | + // new nonce provided in the defer call below. |
206 | + nonce, err := socket.getNonce() |
207 | + if err != nil { |
208 | + return err |
209 | + } |
210 | + defer socket.resetNonce() |
211 | + |
212 | + psum := md5.New() |
213 | + psum.Write([]byte(user + ":mongo:" + pass)) |
214 | + |
215 | + ksum := md5.New() |
216 | + ksum.Write([]byte(nonce + user)) |
217 | + ksum.Write([]byte(hex.EncodeToString(psum.Sum(nil)))) |
218 | + |
219 | + key := hex.EncodeToString(ksum.Sum(nil)) |
220 | + |
221 | + cmd := authCmd{Authenticate: 1, User: user, Nonce: nonce, Key: key} |
222 | + |
223 | + var mutex sync.Mutex |
224 | + var replyErr error |
225 | + mutex.Lock() |
226 | + |
227 | + op := queryOp{} |
228 | + op.query = &cmd |
229 | + op.collection = db + ".$cmd" |
230 | + op.limit = -1 |
231 | + op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) { |
232 | + defer mutex.Unlock() |
233 | + |
234 | + if err != nil { |
235 | + replyErr = err |
236 | + return |
237 | + } |
238 | + |
239 | + // Must handle this within the read loop for the socket, so |
240 | + // that concurrent login requests are properly ordered. |
241 | + result := &authResult{} |
242 | + err = bson.Unmarshal(docData, result) |
243 | + if err != nil { |
244 | + replyErr = err |
245 | + return |
246 | + } |
247 | + if !result.Ok { |
248 | + replyErr = errors.New(result.ErrMsg) |
249 | + } |
250 | + |
251 | + socket.Lock() |
252 | + socket.dropAuth(db) |
253 | + socket.auth = append(socket.auth, authInfo{db, user, pass}) |
254 | + socket.Unlock() |
255 | + } |
256 | + |
257 | + err = socket.Query(&op) |
258 | + if err != nil { |
259 | + return err |
260 | + } |
261 | + mutex.Lock() // Wait. |
262 | + if replyErr != nil { |
263 | + debugf("Socket %p to %s: login error: %s", socket, socket.addr, replyErr) |
264 | + } else { |
265 | + debugf("Socket %p to %s: login successful", socket, socket.addr) |
266 | + } |
267 | + return replyErr |
268 | +} |
269 | + |
270 | +func (socket *mongoSocket) Logout(db string) { |
271 | + socket.Lock() |
272 | + auth, found := socket.dropAuth(db) |
273 | + if found { |
274 | + debugf("Socket %p to %s: logout: db=%q (flagged)", socket, socket.addr, db) |
275 | + socket.logout = append(socket.logout, auth) |
276 | + } |
277 | + socket.Unlock() |
278 | +} |
279 | + |
280 | +func (socket *mongoSocket) LogoutAll() { |
281 | + socket.Lock() |
282 | + if l := len(socket.auth); l > 0 { |
283 | + debugf("Socket %p to %s: logout all (flagged %d)", socket, socket.addr, l) |
284 | + socket.logout = append(socket.logout, socket.auth...) |
285 | + socket.auth = socket.auth[0:0] |
286 | + } |
287 | + socket.Unlock() |
288 | +} |
289 | + |
290 | +func (socket *mongoSocket) flushLogout() (ops []interface{}) { |
291 | + socket.Lock() |
292 | + if l := len(socket.logout); l > 0 { |
293 | + debugf("Socket %p to %s: logout all (flushing %d)", socket, socket.addr, l) |
294 | + for i := 0; i != l; i++ { |
295 | + op := queryOp{} |
296 | + op.query = &logoutCmd{1} |
297 | + op.collection = socket.logout[i].db + ".$cmd" |
298 | + op.limit = -1 |
299 | + ops = append(ops, &op) |
300 | + } |
301 | + socket.logout = socket.logout[0:0] |
302 | + } |
303 | + socket.Unlock() |
304 | + return |
305 | +} |
306 | + |
307 | +func (socket *mongoSocket) dropAuth(db string) (auth authInfo, found bool) { |
308 | + for i, a := range socket.auth { |
309 | + if a.db == db { |
310 | + copy(socket.auth[i:], socket.auth[i+1:]) |
311 | + socket.auth = socket.auth[:len(socket.auth)-1] |
312 | + return a, true |
313 | + } |
314 | + } |
315 | + return auth, false |
316 | +} |
317 | + |
318 | +func (socket *mongoSocket) dropLogout(db, user, pass string) (auth authInfo, found bool) { |
319 | + for i, a := range socket.logout { |
320 | + if a.db == db && a.user == user && a.pass == pass { |
321 | + copy(socket.logout[i:], socket.logout[i+1:]) |
322 | + socket.logout = socket.logout[:len(socket.logout)-1] |
323 | + return a, true |
324 | + } |
325 | + } |
326 | + return auth, false |
327 | +} |
328 | |
329 | === added file 'auth_test.go' |
330 | --- auth_test.go 1970-01-01 00:00:00 +0000 |
331 | +++ auth_test.go 2013-04-03 09:16:27 +0000 |
332 | @@ -0,0 +1,560 @@ |
333 | +// mgo - MongoDB driver for Go |
334 | +// |
335 | +// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
336 | +// |
337 | +// All rights reserved. |
338 | +// |
339 | +// Redistribution and use in source and binary forms, with or without |
340 | +// modification, are permitted provided that the following conditions are met: |
341 | +// |
342 | +// 1. Redistributions of source code must retain the above copyright notice, this |
343 | +// list of conditions and the following disclaimer. |
344 | +// 2. Redistributions in binary form must reproduce the above copyright notice, |
345 | +// this list of conditions and the following disclaimer in the documentation |
346 | +// and/or other materials provided with the distribution. |
347 | +// |
348 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
349 | +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
350 | +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
351 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
352 | +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
353 | +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
354 | +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
355 | +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
356 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
357 | +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
358 | + |
359 | +package mgo_test |
360 | + |
361 | +import ( |
362 | + . "launchpad.net/gocheck" |
363 | + "labix.org/v2/mgo" |
364 | + "sync" |
365 | +) |
366 | + |
367 | +func (s *S) TestAuthLogin(c *C) { |
368 | + session, err := mgo.Dial("localhost:40002") |
369 | + c.Assert(err, IsNil) |
370 | + defer session.Close() |
371 | + |
372 | + coll := session.DB("mydb").C("mycoll") |
373 | + err = coll.Insert(M{"n": 1}) |
374 | + c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*") |
375 | + |
376 | + admindb := session.DB("admin") |
377 | + |
378 | + err = admindb.Login("root", "wrong") |
379 | + c.Assert(err, ErrorMatches, "auth fails") |
380 | + |
381 | + err = admindb.Login("root", "rapadura") |
382 | + c.Assert(err, IsNil) |
383 | + |
384 | + err = coll.Insert(M{"n": 1}) |
385 | + c.Assert(err, IsNil) |
386 | +} |
387 | + |
388 | +func (s *S) TestAuthLoginLogout(c *C) { |
389 | + session, err := mgo.Dial("localhost:40002") |
390 | + c.Assert(err, IsNil) |
391 | + defer session.Close() |
392 | + |
393 | + admindb := session.DB("admin") |
394 | + err = admindb.Login("root", "rapadura") |
395 | + c.Assert(err, IsNil) |
396 | + |
397 | + admindb.Logout() |
398 | + |
399 | + coll := session.DB("mydb").C("mycoll") |
400 | + err = coll.Insert(M{"n": 1}) |
401 | + c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*") |
402 | + |
403 | + // Must have dropped auth from the session too. |
404 | + session = session.Copy() |
405 | + defer session.Close() |
406 | + |
407 | + coll = session.DB("mydb").C("mycoll") |
408 | + err = coll.Insert(M{"n": 1}) |
409 | + c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*") |
410 | +} |
411 | + |
412 | +func (s *S) TestAuthLoginLogoutAll(c *C) { |
413 | + session, err := mgo.Dial("localhost:40002") |
414 | + c.Assert(err, IsNil) |
415 | + defer session.Close() |
416 | + |
417 | + admindb := session.DB("admin") |
418 | + err = admindb.Login("root", "rapadura") |
419 | + c.Assert(err, IsNil) |
420 | + |
421 | + session.LogoutAll() |
422 | + |
423 | + coll := session.DB("mydb").C("mycoll") |
424 | + err = coll.Insert(M{"n": 1}) |
425 | + c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*") |
426 | + |
427 | + // Must have dropped auth from the session too. |
428 | + session = session.Copy() |
429 | + defer session.Close() |
430 | + |
431 | + coll = session.DB("mydb").C("mycoll") |
432 | + err = coll.Insert(M{"n": 1}) |
433 | + c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized .*") |
434 | +} |
435 | + |
436 | +func (s *S) TestAuthAddUser(c *C) { |
437 | + session, err := mgo.Dial("localhost:40002") |
438 | + c.Assert(err, IsNil) |
439 | + defer session.Close() |
440 | + |
441 | + admindb := session.DB("admin") |
442 | + err = admindb.Login("root", "rapadura") |
443 | + c.Assert(err, IsNil) |
444 | + |
445 | + mydb := session.DB("mydb") |
446 | + err = mydb.AddUser("myruser", "mypass", true) |
447 | + c.Assert(err, IsNil) |
448 | + err = mydb.AddUser("mywuser", "mypass", false) |
449 | + c.Assert(err, IsNil) |
450 | + |
451 | + err = mydb.Login("myruser", "mypass") |
452 | + c.Assert(err, IsNil) |
453 | + |
454 | + admindb.Logout() |
455 | + |
456 | + coll := session.DB("mydb").C("mycoll") |
457 | + err = coll.Insert(M{"n": 1}) |
458 | + c.Assert(err, ErrorMatches, "unauthorized|not authorized .*") |
459 | + |
460 | + err = mydb.Login("mywuser", "mypass") |
461 | + c.Assert(err, IsNil) |
462 | + |
463 | + err = coll.Insert(M{"n": 1}) |
464 | + c.Assert(err, IsNil) |
465 | +} |
466 | + |
467 | +func (s *S) TestAuthAddUserReplaces(c *C) { |
468 | + session, err := mgo.Dial("localhost:40002") |
469 | + c.Assert(err, IsNil) |
470 | + defer session.Close() |
471 | + |
472 | + admindb := session.DB("admin") |
473 | + err = admindb.Login("root", "rapadura") |
474 | + c.Assert(err, IsNil) |
475 | + |
476 | + mydb := session.DB("mydb") |
477 | + err = mydb.AddUser("myuser", "myoldpass", false) |
478 | + c.Assert(err, IsNil) |
479 | + err = mydb.AddUser("myuser", "mynewpass", true) |
480 | + c.Assert(err, IsNil) |
481 | + |
482 | + admindb.Logout() |
483 | + |
484 | + err = mydb.Login("myuser", "myoldpass") |
485 | + c.Assert(err, ErrorMatches, "auth fails") |
486 | + err = mydb.Login("myuser", "mynewpass") |
487 | + c.Assert(err, IsNil) |
488 | + |
489 | + // ReadOnly flag was changed too. |
490 | + err = mydb.C("mycoll").Insert(M{"n": 1}) |
491 | + c.Assert(err, ErrorMatches, "unauthorized|not authorized .*") |
492 | +} |
493 | + |
494 | +func (s *S) TestAuthRemoveUser(c *C) { |
495 | + session, err := mgo.Dial("localhost:40002") |
496 | + c.Assert(err, IsNil) |
497 | + defer session.Close() |
498 | + |
499 | + admindb := session.DB("admin") |
500 | + err = admindb.Login("root", "rapadura") |
501 | + c.Assert(err, IsNil) |
502 | + |
503 | + mydb := session.DB("mydb") |
504 | + err = mydb.AddUser("myuser", "mypass", true) |
505 | + c.Assert(err, IsNil) |
506 | + err = mydb.RemoveUser("myuser") |
507 | + c.Assert(err, IsNil) |
508 | + |
509 | + err = mydb.Login("myuser", "mypass") |
510 | + c.Assert(err, ErrorMatches, "auth fails") |
511 | +} |
512 | + |
513 | +func (s *S) TestAuthLoginTwiceDoesNothing(c *C) { |
514 | + session, err := mgo.Dial("localhost:40002") |
515 | + c.Assert(err, IsNil) |
516 | + defer session.Close() |
517 | + |
518 | + admindb := session.DB("admin") |
519 | + err = admindb.Login("root", "rapadura") |
520 | + c.Assert(err, IsNil) |
521 | + |
522 | + oldStats := mgo.GetStats() |
523 | + |
524 | + err = admindb.Login("root", "rapadura") |
525 | + c.Assert(err, IsNil) |
526 | + |
527 | + newStats := mgo.GetStats() |
528 | + c.Assert(newStats.SentOps, Equals, oldStats.SentOps) |
529 | +} |
530 | + |
531 | +func (s *S) TestAuthLoginLogoutLoginDoesNothing(c *C) { |
532 | + session, err := mgo.Dial("localhost:40002") |
533 | + c.Assert(err, IsNil) |
534 | + defer session.Close() |
535 | + |
536 | + admindb := session.DB("admin") |
537 | + err = admindb.Login("root", "rapadura") |
538 | + c.Assert(err, IsNil) |
539 | + |
540 | + oldStats := mgo.GetStats() |
541 | + |
542 | + admindb.Logout() |
543 | + err = admindb.Login("root", "rapadura") |
544 | + c.Assert(err, IsNil) |
545 | + |
546 | + newStats := mgo.GetStats() |
547 | + c.Assert(newStats.SentOps, Equals, oldStats.SentOps) |
548 | +} |
549 | + |
550 | +func (s *S) TestAuthLoginSwitchUser(c *C) { |
551 | + session, err := mgo.Dial("localhost:40002") |
552 | + c.Assert(err, IsNil) |
553 | + defer session.Close() |
554 | + |
555 | + admindb := session.DB("admin") |
556 | + err = admindb.Login("root", "rapadura") |
557 | + c.Assert(err, IsNil) |
558 | + |
559 | + coll := session.DB("mydb").C("mycoll") |
560 | + err = coll.Insert(M{"n": 1}) |
561 | + c.Assert(err, IsNil) |
562 | + |
563 | + err = admindb.Login("reader", "rapadura") |
564 | + c.Assert(err, IsNil) |
565 | + |
566 | + // Can't write. |
567 | + err = coll.Insert(M{"n": 1}) |
568 | + c.Assert(err, ErrorMatches, "unauthorized|not authorized .*") |
569 | + |
570 | + // But can read. |
571 | + result := struct{ N int }{} |
572 | + err = coll.Find(nil).One(&result) |
573 | + c.Assert(err, IsNil) |
574 | + c.Assert(result.N, Equals, 1) |
575 | +} |
576 | + |
577 | +func (s *S) TestAuthLoginChangePassword(c *C) { |
578 | + session, err := mgo.Dial("localhost:40002") |
579 | + c.Assert(err, IsNil) |
580 | + defer session.Close() |
581 | + |
582 | + admindb := session.DB("admin") |
583 | + err = admindb.Login("root", "rapadura") |
584 | + c.Assert(err, IsNil) |
585 | + |
586 | + mydb := session.DB("mydb") |
587 | + err = mydb.AddUser("myuser", "myoldpass", false) |
588 | + c.Assert(err, IsNil) |
589 | + |
590 | + err = mydb.Login("myuser", "myoldpass") |
591 | + c.Assert(err, IsNil) |
592 | + |
593 | + err = mydb.AddUser("myuser", "mynewpass", true) |
594 | + c.Assert(err, IsNil) |
595 | + |
596 | + err = mydb.Login("myuser", "mynewpass") |
597 | + c.Assert(err, IsNil) |
598 | + |
599 | + admindb.Logout() |
600 | + |
601 | + // The second login must be in effect, which means read-only. |
602 | + err = mydb.C("mycoll").Insert(M{"n": 1}) |
603 | + c.Assert(err, ErrorMatches, "unauthorized|not authorized .*") |
604 | +} |
605 | + |
606 | +func (s *S) TestAuthLoginCachingWithSessionRefresh(c *C) { |
607 | + session, err := mgo.Dial("localhost:40002") |
608 | + c.Assert(err, IsNil) |
609 | + defer session.Close() |
610 | + |
611 | + admindb := session.DB("admin") |
612 | + err = admindb.Login("root", "rapadura") |
613 | + c.Assert(err, IsNil) |
614 | + |
615 | + session.Refresh() |
616 | + |
617 | + coll := session.DB("mydb").C("mycoll") |
618 | + err = coll.Insert(M{"n": 1}) |
619 | + c.Assert(err, IsNil) |
620 | +} |
621 | + |
622 | +func (s *S) TestAuthLoginCachingWithSessionCopy(c *C) { |
623 | + session, err := mgo.Dial("localhost:40002") |
624 | + c.Assert(err, IsNil) |
625 | + defer session.Close() |
626 | + |
627 | + admindb := session.DB("admin") |
628 | + err = admindb.Login("root", "rapadura") |
629 | + c.Assert(err, IsNil) |
630 | + |
631 | + session = session.Copy() |
632 | + defer session.Close() |
633 | + |
634 | + coll := session.DB("mydb").C("mycoll") |
635 | + err = coll.Insert(M{"n": 1}) |
636 | + c.Assert(err, IsNil) |
637 | +} |
638 | + |
639 | +func (s *S) TestAuthLoginCachingWithSessionClone(c *C) { |
640 | + session, err := mgo.Dial("localhost:40002") |
641 | + c.Assert(err, IsNil) |
642 | + defer session.Close() |
643 | + |
644 | + admindb := session.DB("admin") |
645 | + err = admindb.Login("root", "rapadura") |
646 | + c.Assert(err, IsNil) |
647 | + |
648 | + session = session.Clone() |
649 | + defer session.Close() |
650 | + |
651 | + coll := session.DB("mydb").C("mycoll") |
652 | + err = coll.Insert(M{"n": 1}) |
653 | + c.Assert(err, IsNil) |
654 | +} |
655 | + |
656 | +func (s *S) TestAuthLoginCachingWithNewSession(c *C) { |
657 | + session, err := mgo.Dial("localhost:40002") |
658 | + c.Assert(err, IsNil) |
659 | + defer session.Close() |
660 | + |
661 | + admindb := session.DB("admin") |
662 | + err = admindb.Login("root", "rapadura") |
663 | + c.Assert(err, IsNil) |
664 | + |
665 | + session = session.New() |
666 | + defer session.Close() |
667 | + |
668 | + coll := session.DB("mydb").C("mycoll") |
669 | + err = coll.Insert(M{"n": 1}) |
670 | + c.Assert(err, ErrorMatches, "unauthorized|need to login|not authorized for .*") |
671 | +} |
672 | + |
673 | +func (s *S) TestAuthLoginCachingAcrossPool(c *C) { |
674 | + // Logins are cached even when the conenction goes back |
675 | + // into the pool. |
676 | + |
677 | + session, err := mgo.Dial("localhost:40002") |
678 | + c.Assert(err, IsNil) |
679 | + defer session.Close() |
680 | + |
681 | + admindb := session.DB("admin") |
682 | + err = admindb.Login("root", "rapadura") |
683 | + c.Assert(err, IsNil) |
684 | + |
685 | + // Add another user to test the logout case at the same time. |
686 | + mydb := session.DB("mydb") |
687 | + err = mydb.AddUser("myuser", "mypass", false) |
688 | + c.Assert(err, IsNil) |
689 | + |
690 | + err = mydb.Login("myuser", "mypass") |
691 | + c.Assert(err, IsNil) |
692 | + |
693 | + // Logout root explicitly, to test both cases. |
694 | + admindb.Logout() |
695 | + |
696 | + // Give socket back to pool. |
697 | + session.Refresh() |
698 | + |
699 | + // Brand new session, should use socket from the pool. |
700 | + other := session.New() |
701 | + defer other.Close() |
702 | + |
703 | + oldStats := mgo.GetStats() |
704 | + |
705 | + err = other.DB("admin").Login("root", "rapadura") |
706 | + c.Assert(err, IsNil) |
707 | + err = other.DB("mydb").Login("myuser", "mypass") |
708 | + c.Assert(err, IsNil) |
709 | + |
710 | + // Both logins were cached, so no ops. |
711 | + newStats := mgo.GetStats() |
712 | + c.Assert(newStats.SentOps, Equals, oldStats.SentOps) |
713 | + |
714 | + // And they actually worked. |
715 | + err = other.DB("mydb").C("mycoll").Insert(M{"n": 1}) |
716 | + c.Assert(err, IsNil) |
717 | + |
718 | + other.DB("admin").Logout() |
719 | + |
720 | + err = other.DB("mydb").C("mycoll").Insert(M{"n": 1}) |
721 | + c.Assert(err, IsNil) |
722 | +} |
723 | + |
724 | +func (s *S) TestAuthLoginCachingAcrossPoolWithLogout(c *C) { |
725 | + // Now verify that logouts are properly flushed if they |
726 | + // are not revalidated after leaving the pool. |
727 | + |
728 | + session, err := mgo.Dial("localhost:40002") |
729 | + c.Assert(err, IsNil) |
730 | + defer session.Close() |
731 | + |
732 | + admindb := session.DB("admin") |
733 | + err = admindb.Login("root", "rapadura") |
734 | + c.Assert(err, IsNil) |
735 | + |
736 | + // Add another user to test the logout case at the same time. |
737 | + mydb := session.DB("mydb") |
738 | + err = mydb.AddUser("myuser", "mypass", true) |
739 | + c.Assert(err, IsNil) |
740 | + |
741 | + err = mydb.Login("myuser", "mypass") |
742 | + c.Assert(err, IsNil) |
743 | + |
744 | + // Just some data to query later. |
745 | + err = session.DB("mydb").C("mycoll").Insert(M{"n": 1}) |
746 | + c.Assert(err, IsNil) |
747 | + |
748 | + // Give socket back to pool. |
749 | + session.Refresh() |
750 | + |
751 | + // Brand new session, should use socket from the pool. |
752 | + other := session.New() |
753 | + defer other.Close() |
754 | + |
755 | + oldStats := mgo.GetStats() |
756 | + |
757 | + err = other.DB("mydb").Login("myuser", "mypass") |
758 | + c.Assert(err, IsNil) |
759 | + |
760 | + // Login was cached, so no ops. |
761 | + newStats := mgo.GetStats() |
762 | + c.Assert(newStats.SentOps, Equals, oldStats.SentOps) |
763 | + |
764 | + // Can't write, since root has been implicitly logged out |
765 | + // when the collection went into the pool, and not revalidated. |
766 | + err = other.DB("mydb").C("mycoll").Insert(M{"n": 1}) |
767 | + c.Assert(err, ErrorMatches, "unauthorized|not authorized .*") |
768 | + |
769 | + // But can read due to the revalidated myuser login. |
770 | + result := struct{ N int }{} |
771 | + err = other.DB("mydb").C("mycoll").Find(nil).One(&result) |
772 | + c.Assert(err, IsNil) |
773 | + c.Assert(result.N, Equals, 1) |
774 | +} |
775 | + |
776 | +func (s *S) TestAuthEventual(c *C) { |
777 | + // Eventual sessions don't keep sockets around, so they are |
778 | + // an interesting test case. |
779 | + session, err := mgo.Dial("localhost:40002") |
780 | + c.Assert(err, IsNil) |
781 | + defer session.Close() |
782 | + |
783 | + admindb := session.DB("admin") |
784 | + err = admindb.Login("root", "rapadura") |
785 | + c.Assert(err, IsNil) |
786 | + |
787 | + err = session.DB("mydb").C("mycoll").Insert(M{"n": 1}) |
788 | + c.Assert(err, IsNil) |
789 | + |
790 | + var wg sync.WaitGroup |
791 | + wg.Add(20) |
792 | + |
793 | + for i := 0; i != 10; i++ { |
794 | + go func() { |
795 | + defer wg.Done() |
796 | + var result struct{ N int } |
797 | + err := session.DB("mydb").C("mycoll").Find(nil).One(&result) |
798 | + c.Assert(err, IsNil) |
799 | + c.Assert(result.N, Equals, 1) |
800 | + }() |
801 | + } |
802 | + |
803 | + for i := 0; i != 10; i++ { |
804 | + go func() { |
805 | + defer wg.Done() |
806 | + err := session.DB("mydb").C("mycoll").Insert(M{"n": 1}) |
807 | + c.Assert(err, IsNil) |
808 | + }() |
809 | + } |
810 | + |
811 | + wg.Wait() |
812 | +} |
813 | + |
814 | +func (s *S) TestAuthURL(c *C) { |
815 | + session, err := mgo.Dial("mongodb://root:rapadura@localhost:40002/") |
816 | + c.Assert(err, IsNil) |
817 | + defer session.Close() |
818 | + |
819 | + err = session.DB("mydb").C("mycoll").Insert(M{"n": 1}) |
820 | + c.Assert(err, IsNil) |
821 | +} |
822 | + |
823 | +func (s *S) TestAuthURLWrongCredentials(c *C) { |
824 | + session, err := mgo.Dial("mongodb://root:wrong@localhost:40002/") |
825 | + if session != nil { |
826 | + session.Close() |
827 | + } |
828 | + c.Assert(err, ErrorMatches, "auth fails") |
829 | + c.Assert(session, IsNil) |
830 | +} |
831 | + |
832 | +func (s *S) TestAuthURLWithNewSession(c *C) { |
833 | + // When authentication is in the URL, the new session will |
834 | + // actually carry it on as well, even if logged out explicitly. |
835 | + session, err := mgo.Dial("mongodb://root:rapadura@localhost:40002/") |
836 | + c.Assert(err, IsNil) |
837 | + defer session.Close() |
838 | + |
839 | + session.DB("admin").Logout() |
840 | + |
841 | + // Do it twice to ensure it passes the needed data on. |
842 | + session = session.New() |
843 | + defer session.Close() |
844 | + session = session.New() |
845 | + defer session.Close() |
846 | + |
847 | + err = session.DB("mydb").C("mycoll").Insert(M{"n": 1}) |
848 | + c.Assert(err, IsNil) |
849 | +} |
850 | + |
851 | +func (s *S) TestAuthURLWithDatabase(c *C) { |
852 | + session, err := mgo.Dial("mongodb://root:rapadura@localhost:40002") |
853 | + c.Assert(err, IsNil) |
854 | + defer session.Close() |
855 | + |
856 | + mydb := session.DB("mydb") |
857 | + err = mydb.AddUser("myruser", "mypass", true) |
858 | + c.Assert(err, IsNil) |
859 | + |
860 | + usession, err := mgo.Dial("mongodb://myruser:mypass@localhost:40002/mydb") |
861 | + c.Assert(err, IsNil) |
862 | + defer usession.Close() |
863 | + |
864 | + ucoll := usession.DB("mydb").C("mycoll") |
865 | + err = ucoll.FindId(0).One(nil) |
866 | + c.Assert(err, Equals, mgo.ErrNotFound) |
867 | + err = ucoll.Insert(M{"n": 1}) |
868 | + c.Assert(err, ErrorMatches, "unauthorized|not authorized .*") |
869 | +} |
870 | + |
871 | +func (s *S) TestDefaultDatabase(c *C) { |
872 | + tests := []struct{ url, db string }{ |
873 | + {"mongodb://root:rapadura@localhost:40002", "test"}, |
874 | + {"mongodb://root:rapadura@localhost:40002/admin", "admin"}, |
875 | + {"mongodb://localhost:40001", "test"}, |
876 | + {"mongodb://localhost:40001/", "test"}, |
877 | + {"mongodb://localhost:40001/mydb", "mydb"}, |
878 | + } |
879 | + |
880 | + for _, test := range tests { |
881 | + session, err := mgo.Dial(test.url) |
882 | + c.Assert(err, IsNil) |
883 | + defer session.Close() |
884 | + |
885 | + c.Logf("test: %#v", test) |
886 | + c.Assert(session.DB("").Name, Equals, test.db) |
887 | + |
888 | + scopy := session.Copy() |
889 | + c.Check(scopy.DB("").Name, Equals, test.db) |
890 | + scopy.Close() |
891 | + } |
892 | +} |
893 | |
894 | === added directory 'bson' |
895 | === renamed directory 'bson' => 'bson.moved' |
896 | === added file 'bson/LICENSE' |
897 | --- bson/LICENSE 1970-01-01 00:00:00 +0000 |
898 | +++ bson/LICENSE 2013-04-03 09:16:27 +0000 |
899 | @@ -0,0 +1,25 @@ |
900 | +BSON library for Go |
901 | + |
902 | +Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
903 | + |
904 | +All rights reserved. |
905 | + |
906 | +Redistribution and use in source and binary forms, with or without |
907 | +modification, are permitted provided that the following conditions are met: |
908 | + |
909 | +1. Redistributions of source code must retain the above copyright notice, this |
910 | + list of conditions and the following disclaimer. |
911 | +2. Redistributions in binary form must reproduce the above copyright notice, |
912 | + this list of conditions and the following disclaimer in the documentation |
913 | + and/or other materials provided with the distribution. |
914 | + |
915 | +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
916 | +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
917 | +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
918 | +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
919 | +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
920 | +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
921 | +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
922 | +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
923 | +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
924 | +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
925 | |
926 | === added file 'bson/bson.go' |
927 | --- bson/bson.go 1970-01-01 00:00:00 +0000 |
928 | +++ bson/bson.go 2013-04-03 09:16:27 +0000 |
929 | @@ -0,0 +1,639 @@ |
930 | +// BSON library for Go |
931 | +// |
932 | +// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
933 | +// |
934 | +// All rights reserved. |
935 | +// |
936 | +// Redistribution and use in source and binary forms, with or without |
937 | +// modification, are permitted provided that the following conditions are met: |
938 | +// |
939 | +// 1. Redistributions of source code must retain the above copyright notice, this |
940 | +// list of conditions and the following disclaimer. |
941 | +// 2. Redistributions in binary form must reproduce the above copyright notice, |
942 | +// this list of conditions and the following disclaimer in the documentation |
943 | +// and/or other materials provided with the distribution. |
944 | +// |
945 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
946 | +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
947 | +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
948 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
949 | +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
950 | +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
951 | +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
952 | +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
953 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
954 | +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
955 | + |
956 | +package bson |
957 | + |
958 | +import ( |
959 | + "crypto/md5" |
960 | + "crypto/rand" |
961 | + "encoding/binary" |
962 | + "encoding/hex" |
963 | + "errors" |
964 | + "fmt" |
965 | + "io" |
966 | + "os" |
967 | + "reflect" |
968 | + "runtime" |
969 | + "strings" |
970 | + "sync" |
971 | + "sync/atomic" |
972 | + "time" |
973 | +) |
974 | + |
975 | +// -------------------------------------------------------------------------- |
976 | +// The public API. |
977 | + |
978 | +// A value implementing the bson.Getter interface will have its GetBSON |
979 | +// method called when the given value has to be marshalled, and the result |
980 | +// of this method will be marshaled in place of the actual object. |
981 | +// |
982 | +// If GetBSON returns return a non-nil error, the marshalling procedure |
983 | +// will stop and error out with the provided value. |
984 | +type Getter interface { |
985 | + GetBSON() (interface{}, error) |
986 | +} |
987 | + |
988 | +// A value implementing the bson.Setter interface will receive the BSON |
989 | +// value via the SetBSON method during unmarshaling, and the object |
990 | +// itself will not be changed as usual. |
991 | +// |
992 | +// If setting the value works, the method should return nil or alternatively |
993 | +// bson.SetZero to set the respective field to its zero value (nil for |
994 | +// pointer types). If SetBSON returns a value of type bson.TypeError, the |
995 | +// BSON value will be omitted from a map or slice being decoded and the |
996 | +// unmarshalling will continue. If it returns any other non-nil error, the |
997 | +// unmarshalling procedure will stop and error out with the provided value. |
998 | +// |
999 | +// This interface is generally useful in pointer receivers, since the method |
1000 | +// will want to change the receiver. A type field that implements the Setter |
1001 | +// interface doesn't have to be a pointer, though. |
1002 | +// |
1003 | +// Unlike the usual behavior, unmarshalling onto a value that implements a |
1004 | +// Setter interface will NOT reset the value to its zero state. This allows |
1005 | +// the value to decide by itself how to be unmarshalled. |
1006 | +// |
1007 | +// For example: |
1008 | +// |
1009 | +// type MyString string |
1010 | +// |
1011 | +// func (s *MyString) SetBSON(raw bson.Raw) error { |
1012 | +// return raw.Unmarshal(s) |
1013 | +// } |
1014 | +// |
1015 | +type Setter interface { |
1016 | + SetBSON(raw Raw) error |
1017 | +} |
1018 | + |
1019 | +// SetZero may be returned from a SetBSON method to have the value set to |
1020 | +// its respective zero value. When used in pointer values, this will set the |
1021 | +// field to nil rather than to the pre-allocated value. |
1022 | +var SetZero = errors.New("set to zero") |
1023 | + |
1024 | +// M is a convenient alias for a map[string]interface{} map, useful for |
1025 | +// dealing with BSON in a native way. For instance: |
1026 | +// |
1027 | +// bson.M{"a": 1, "b": true} |
1028 | +// |
1029 | +// There's no special handling for this type in addition to what's done anyway |
1030 | +// for an equivalent map type. Elements in the map will be dumped in an |
1031 | +// undefined ordered. See also the bson.D type for an ordered alternative. |
1032 | +type M map[string]interface{} |
1033 | + |
1034 | +// D is a type for dealing with documents containing ordered elements in a |
1035 | +// native fashion. For instance: |
1036 | +// |
1037 | +// bson.D{{"a", 1}, {"b", true}} |
1038 | +// |
1039 | +// In some situations, such as when creating indexes for MongoDB, the order in |
1040 | +// which the elements are defined is important. If the order is not important, |
1041 | +// using a map is generally more comfortable. See the bson.M type and the |
1042 | +// Map() method for D. |
1043 | +type D []DocElem |
1044 | + |
1045 | +// See the bson.D type. |
1046 | +type DocElem struct { |
1047 | + Name string |
1048 | + Value interface{} |
1049 | +} |
1050 | + |
1051 | +// The Raw type represents raw unprocessed BSON documents and elements. |
1052 | +// Kind is the kind of element as defined per the BSON specification, and |
1053 | +// Data is the raw unprocessed data for the respective element. |
1054 | +// Using this type it is possible to unmarshal or marshal values partially. |
1055 | +// |
1056 | +// Relevant documentation: |
1057 | +// |
1058 | +// http://bsonspec.org/#/specification |
1059 | +// |
1060 | +type Raw struct { |
1061 | + Kind byte |
1062 | + Data []byte |
1063 | +} |
1064 | + |
1065 | +// Map returns a map out of the ordered element name/value pairs in d. |
1066 | +func (d D) Map() (m M) { |
1067 | + m = make(M, len(d)) |
1068 | + for _, item := range d { |
1069 | + m[item.Name] = item.Value |
1070 | + } |
1071 | + return m |
1072 | +} |
1073 | + |
1074 | +// ObjectId is a unique ID identifying a BSON value. It must be exactly 12 bytes |
1075 | +// long. MongoDB objects by default have such a property set in their "_id" |
1076 | +// property. |
1077 | +// |
1078 | +// http://www.mongodb.org/display/DOCS/Object+IDs |
1079 | +type ObjectId string |
1080 | + |
1081 | +// ObjectIdHex returns an ObjectId from the provided hex representation. |
1082 | +// Calling this function with an invalid hex representation will |
1083 | +// cause a runtime panic. See the IsObjectIdHex function. |
1084 | +func ObjectIdHex(s string) ObjectId { |
1085 | + d, err := hex.DecodeString(s) |
1086 | + if err != nil || len(d) != 12 { |
1087 | + panic(fmt.Sprintf("Invalid input to ObjectIdHex: %q", s)) |
1088 | + } |
1089 | + return ObjectId(d) |
1090 | +} |
1091 | + |
1092 | +// IsObjectIdHex returns whether s is a valid hex representation of |
1093 | +// an ObjectId. See the ObjectIdHex function. |
1094 | +func IsObjectIdHex(s string) bool { |
1095 | + if len(s) != 24 { |
1096 | + return false |
1097 | + } |
1098 | + _, err := hex.DecodeString(s) |
1099 | + return err == nil |
1100 | +} |
1101 | + |
1102 | +// objectIdCounter is atomically incremented when generating a new ObjectId |
1103 | +// using NewObjectId() function. It's used as a counter part of an id. |
1104 | +var objectIdCounter uint32 = 0 |
1105 | + |
1106 | +// machineId stores machine id generated once and used in subsequent calls |
1107 | +// to NewObjectId function. |
1108 | +var machineId = readMachineId() |
1109 | + |
1110 | +// initMachineId generates machine id and puts it into the machineId global |
1111 | +// variable. If this function fails to get the hostname, it will cause |
1112 | +// a runtime error. |
1113 | +func readMachineId() []byte { |
1114 | + var sum [3]byte |
1115 | + id := sum[:] |
1116 | + hostname, err1 := os.Hostname() |
1117 | + if err1 != nil { |
1118 | + _, err2 := io.ReadFull(rand.Reader, id) |
1119 | + if err2 != nil { |
1120 | + panic(fmt.Errorf("cannot get hostname: %v; %v", err1, err2)) |
1121 | + } |
1122 | + return id |
1123 | + } |
1124 | + hw := md5.New() |
1125 | + hw.Write([]byte(hostname)) |
1126 | + copy(id, hw.Sum(nil)) |
1127 | + return id |
1128 | +} |
1129 | + |
1130 | +// NewObjectId returns a new unique ObjectId. |
1131 | +// This function causes a runtime error if it fails to get the hostname |
1132 | +// of the current machine. |
1133 | +func NewObjectId() ObjectId { |
1134 | + var b [12]byte |
1135 | + // Timestamp, 4 bytes, big endian |
1136 | + binary.BigEndian.PutUint32(b[:], uint32(time.Now().Unix())) |
1137 | + // Machine, first 3 bytes of md5(hostname) |
1138 | + b[4] = machineId[0] |
1139 | + b[5] = machineId[1] |
1140 | + b[6] = machineId[2] |
1141 | + // Pid, 2 bytes, specs don't specify endianness, but we use big endian. |
1142 | + pid := os.Getpid() |
1143 | + b[7] = byte(pid >> 8) |
1144 | + b[8] = byte(pid) |
1145 | + // Increment, 3 bytes, big endian |
1146 | + i := atomic.AddUint32(&objectIdCounter, 1) |
1147 | + b[9] = byte(i >> 16) |
1148 | + b[10] = byte(i >> 8) |
1149 | + b[11] = byte(i) |
1150 | + return ObjectId(b[:]) |
1151 | +} |
1152 | + |
1153 | +// NewObjectIdWithTime returns a dummy ObjectId with the timestamp part filled |
1154 | +// with the provided number of seconds from epoch UTC, and all other parts |
1155 | +// filled with zeroes. It's not safe to insert a document with an id generated |
1156 | +// by this method, it is useful only for queries to find documents with ids |
1157 | +// generated before or after the specified timestamp. |
1158 | +func NewObjectIdWithTime(t time.Time) ObjectId { |
1159 | + var b [12]byte |
1160 | + binary.BigEndian.PutUint32(b[:4], uint32(t.Unix())) |
1161 | + return ObjectId(string(b[:])) |
1162 | +} |
1163 | + |
1164 | +// String returns a hex string representation of the id. |
1165 | +// Example: ObjectIdHex("4d88e15b60f486e428412dc9"). |
1166 | +func (id ObjectId) String() string { |
1167 | + return fmt.Sprintf(`ObjectIdHex("%x")`, string(id)) |
1168 | +} |
1169 | + |
1170 | +// Hex returns a hex representation of the ObjectId. |
1171 | +func (id ObjectId) Hex() string { |
1172 | + return hex.EncodeToString([]byte(id)) |
1173 | +} |
1174 | + |
1175 | +// MarshalJSON turns a bson.ObjectId into a json.Marshaller. |
1176 | +func (id ObjectId) MarshalJSON() ([]byte, error) { |
1177 | + return []byte(fmt.Sprintf(`"%x"`, string(id))), nil |
1178 | +} |
1179 | + |
1180 | +// UnmarshalJSON turns *bson.ObjectId into a json.Unmarshaller. |
1181 | +func (id *ObjectId) UnmarshalJSON(data []byte) error { |
1182 | + if len(data) != 26 || data[0] != '"' || data[25] != '"' { |
1183 | + return errors.New(fmt.Sprintf("Invalid ObjectId in JSON: %s", string(data))) |
1184 | + } |
1185 | + var buf [12]byte |
1186 | + _, err := hex.Decode(buf[:], data[1:25]) |
1187 | + if err != nil { |
1188 | + return errors.New(fmt.Sprintf("Invalid ObjectId in JSON: %s (%s)", string(data), err)) |
1189 | + } |
1190 | + *id = ObjectId(string(buf[:])) |
1191 | + return nil |
1192 | +} |
1193 | + |
1194 | +// Valid returns true if id is valid. A valid id must contain exactly 12 bytes. |
1195 | +func (id ObjectId) Valid() bool { |
1196 | + return len(id) == 12 |
1197 | +} |
1198 | + |
1199 | +// byteSlice returns byte slice of id from start to end. |
1200 | +// Calling this function with an invalid id will cause a runtime panic. |
1201 | +func (id ObjectId) byteSlice(start, end int) []byte { |
1202 | + if len(id) != 12 { |
1203 | + panic(fmt.Sprintf("Invalid ObjectId: %q", string(id))) |
1204 | + } |
1205 | + return []byte(string(id)[start:end]) |
1206 | +} |
1207 | + |
1208 | +// Time returns the timestamp part of the id. |
1209 | +// It's a runtime error to call this method with an invalid id. |
1210 | +func (id ObjectId) Time() time.Time { |
1211 | + // First 4 bytes of ObjectId is 32-bit big-endian seconds from epoch. |
1212 | + secs := int64(binary.BigEndian.Uint32(id.byteSlice(0, 4))) |
1213 | + return time.Unix(secs, 0) |
1214 | +} |
1215 | + |
1216 | +// Machine returns the 3-byte machine id part of the id. |
1217 | +// It's a runtime error to call this method with an invalid id. |
1218 | +func (id ObjectId) Machine() []byte { |
1219 | + return id.byteSlice(4, 7) |
1220 | +} |
1221 | + |
1222 | +// Pid returns the process id part of the id. |
1223 | +// It's a runtime error to call this method with an invalid id. |
1224 | +func (id ObjectId) Pid() uint16 { |
1225 | + return binary.BigEndian.Uint16(id.byteSlice(7, 9)) |
1226 | +} |
1227 | + |
1228 | +// Counter returns the incrementing value part of the id. |
1229 | +// It's a runtime error to call this method with an invalid id. |
1230 | +func (id ObjectId) Counter() int32 { |
1231 | + b := id.byteSlice(9, 12) |
1232 | + // Counter is stored as big-endian 3-byte value |
1233 | + return int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])) |
1234 | +} |
1235 | + |
1236 | +// The Symbol type is similar to a string and is used in languages with a |
1237 | +// distinct symbol type. |
1238 | +type Symbol string |
1239 | + |
1240 | +// Now returns the current time with millisecond precision. MongoDB stores |
1241 | +// timestamps with the same precision, so a Time returned from this method |
1242 | +// will not change after a roundtrip to the database. That's the only reason |
1243 | +// why this function exists. Using the time.Now function also works fine |
1244 | +// otherwise. |
1245 | +func Now() time.Time { |
1246 | + return time.Unix(0, time.Now().UnixNano()/1e6*1e6) |
1247 | +} |
1248 | + |
1249 | +// MongoTimestamp is a special internal type used by MongoDB that for some |
1250 | +// strange reason has its own datatype defined in BSON. |
1251 | +type MongoTimestamp int64 |
1252 | + |
1253 | +type orderKey int64 |
1254 | + |
1255 | +// MaxKey is a special value that compares higher than all other possible BSON |
1256 | +// values in a MongoDB database. |
1257 | +var MaxKey = orderKey(1<<63 - 1) |
1258 | + |
1259 | +// MinKey is a special value that compares lower than all other possible BSON |
1260 | +// values in a MongoDB database. |
1261 | +var MinKey = orderKey(-1 << 63) |
1262 | + |
1263 | +type undefined struct{} |
1264 | + |
1265 | +// Undefined represents the undefined BSON value. |
1266 | +var Undefined undefined |
1267 | + |
1268 | +// Binary is a representation for non-standard binary values. Any kind should |
1269 | +// work, but the following are known as of this writing: |
1270 | +// |
1271 | +// 0x00 - Generic. This is decoded as []byte(data), not Binary{0x00, data}. |
1272 | +// 0x01 - Function (!?) |
1273 | +// 0x02 - Obsolete generic. |
1274 | +// 0x03 - UUID |
1275 | +// 0x05 - MD5 |
1276 | +// 0x80 - User defined. |
1277 | +// |
1278 | +type Binary struct { |
1279 | + Kind byte |
1280 | + Data []byte |
1281 | +} |
1282 | + |
1283 | +// RegEx represents a regular expression. The Options field may contain |
1284 | +// individual characters defining the way in which the pattern should be |
1285 | +// applied, and must be sorted. Valid options as of this writing are 'i' for |
1286 | +// case insensitive matching, 'm' for multi-line matching, 'x' for verbose |
1287 | +// mode, 'l' to make \w, \W, and similar be locale-dependent, 's' for dot-all |
1288 | +// mode (a '.' matches everything), and 'u' to make \w, \W, and similar match |
1289 | +// unicode. The value of the Options parameter is not verified before being |
1290 | +// marshaled into the BSON format. |
1291 | +type RegEx struct { |
1292 | + Pattern string |
1293 | + Options string |
1294 | +} |
1295 | + |
1296 | +// JavaScript is a type that holds JavaScript code. If Scope is non-nil, it |
1297 | +// will be marshaled as a mapping from identifiers to values that may be |
1298 | +// used when evaluating the provided Code. |
1299 | +type JavaScript struct { |
1300 | + Code string |
1301 | + Scope interface{} |
1302 | +} |
1303 | + |
1304 | +const initialBufferSize = 64 |
1305 | + |
1306 | +func handleErr(err *error) { |
1307 | + if r := recover(); r != nil { |
1308 | + if _, ok := r.(runtime.Error); ok { |
1309 | + panic(r) |
1310 | + } else if _, ok := r.(externalPanic); ok { |
1311 | + panic(r) |
1312 | + } else if s, ok := r.(string); ok { |
1313 | + *err = errors.New(s) |
1314 | + } else if e, ok := r.(error); ok { |
1315 | + *err = e |
1316 | + } else { |
1317 | + panic(r) |
1318 | + } |
1319 | + } |
1320 | +} |
1321 | + |
1322 | +// Marshal serializes the in value, which may be a map or a struct value. |
1323 | +// In the case of struct values, only exported fields will be serialized. |
1324 | +// The lowercased field name is used as the key for each exported field, |
1325 | +// but this behavior may be changed using the respective field tag. |
1326 | +// The tag may also contain flags to tweak the marshalling behavior for |
1327 | +// the field. The tag formats accepted are: |
1328 | +// |
1329 | +// "[<key>][,<flag1>[,<flag2>]]" |
1330 | +// |
1331 | +// `(...) bson:"[<key>][,<flag1>[,<flag2>]]" (...)` |
1332 | +// |
1333 | +// The following flags are currently supported: |
1334 | +// |
1335 | +// omitempty Only include the field if it's not set to the zero |
1336 | +// value for the type or to empty slices or maps. |
1337 | +// Does not apply to zero valued structs. |
1338 | +// |
1339 | +// minsize Marshal an int64 value as an int32, if that's feasible |
1340 | +// while preserving the numeric value. |
1341 | +// |
1342 | +// inline Inline the field, which must be a struct, causing all |
1343 | +// of its fields to be processed as if they were part of |
1344 | +// the outer struct. |
1345 | +// |
1346 | +// Some examples: |
1347 | +// |
1348 | +// type T struct { |
1349 | +// A bool |
1350 | +// B int "myb" |
1351 | +// C string "myc,omitempty" |
1352 | +// D string `bson:",omitempty" json:"jsonkey"` |
1353 | +// E int64 ",minsize" |
1354 | +// F int64 "myf,omitempty,minsize" |
1355 | +// } |
1356 | +// |
1357 | +func Marshal(in interface{}) (out []byte, err error) { |
1358 | + defer handleErr(&err) |
1359 | + e := &encoder{make([]byte, 0, initialBufferSize)} |
1360 | + e.addDoc(reflect.ValueOf(in)) |
1361 | + return e.out, nil |
1362 | +} |
1363 | + |
1364 | +// Unmarshal deserializes data from in into the out value. The out value |
1365 | +// must be a map, a pointer to a struct, or a pointer to a bson.D value. |
1366 | +// The lowercased field name is used as the key for each exported field, |
1367 | +// but this behavior may be changed using the respective field tag. |
1368 | +// Pointer values are initialized when necessary. |
1369 | +// |
1370 | +// The target field or element types of out may not necessarily match |
1371 | +// the BSON values of the provided data. The following conversions are |
1372 | +// made automatically: |
1373 | +// |
1374 | +// - Numeric types are converted if at least the integer part of the |
1375 | +// value would be preserved correctly |
1376 | +// - Bools are converted to numeric types as 1 or 0 |
1377 | +// - Numeric types are converted to bools as true if not 0 or false otherwise |
1378 | +// - Binary and string BSON data is converted to a string, array or byte slice |
1379 | +// |
1380 | +// If the value would not fit the type and cannot be converted, it's silently |
1381 | +// skipped. |
1382 | +func Unmarshal(in []byte, out interface{}) (err error) { |
1383 | + defer handleErr(&err) |
1384 | + v := reflect.ValueOf(out) |
1385 | + switch v.Kind() { |
1386 | + case reflect.Map, reflect.Ptr: |
1387 | + d := newDecoder(in) |
1388 | + d.readDocTo(v) |
1389 | + case reflect.Struct: |
1390 | + return errors.New("Unmarshal can't deal with struct values. Use a pointer.") |
1391 | + default: |
1392 | + return errors.New("Unmarshal needs a map or a pointer to a struct.") |
1393 | + } |
1394 | + return nil |
1395 | +} |
1396 | + |
1397 | +// Unmarshal deserializes raw into the out value. If the out value type |
1398 | +// is not compatible with raw, a *bson.TypeError is returned. |
1399 | +// |
1400 | +// See the Unmarshal function documentation for more details on the |
1401 | +// unmarshalling process. |
1402 | +func (raw Raw) Unmarshal(out interface{}) (err error) { |
1403 | + defer handleErr(&err) |
1404 | + v := reflect.ValueOf(out) |
1405 | + switch v.Kind() { |
1406 | + case reflect.Ptr: |
1407 | + v = v.Elem() |
1408 | + fallthrough |
1409 | + case reflect.Map: |
1410 | + d := newDecoder(raw.Data) |
1411 | + good := d.readElemTo(v, raw.Kind) |
1412 | + if !good { |
1413 | + return &TypeError{v.Type(), raw.Kind} |
1414 | + } |
1415 | + case reflect.Struct: |
1416 | + return errors.New("Raw Unmarshal can't deal with struct values. Use a pointer.") |
1417 | + default: |
1418 | + return errors.New("Raw Unmarshal needs a map or a valid pointer.") |
1419 | + } |
1420 | + return nil |
1421 | +} |
1422 | + |
1423 | +type TypeError struct { |
1424 | + Type reflect.Type |
1425 | + Kind byte |
1426 | +} |
1427 | + |
1428 | +func (e *TypeError) Error() string { |
1429 | + return fmt.Sprintf("BSON kind 0x%02x isn't compatible with type %s", e.Kind, e.Type.String()) |
1430 | +} |
1431 | + |
1432 | +// -------------------------------------------------------------------------- |
1433 | +// Maintain a mapping of keys to structure field indexes |
1434 | + |
1435 | +type structInfo struct { |
1436 | + FieldsMap map[string]fieldInfo |
1437 | + FieldsList []fieldInfo |
1438 | + Zero reflect.Value |
1439 | +} |
1440 | + |
1441 | +type fieldInfo struct { |
1442 | + Key string |
1443 | + Num int |
1444 | + OmitEmpty bool |
1445 | + MinSize bool |
1446 | + Inline []int |
1447 | +} |
1448 | + |
1449 | +var structMap = make(map[reflect.Type]*structInfo) |
1450 | +var structMapMutex sync.RWMutex |
1451 | + |
1452 | +type externalPanic string |
1453 | + |
1454 | +func (e externalPanic) String() string { |
1455 | + return string(e) |
1456 | +} |
1457 | + |
1458 | +func getStructInfo(st reflect.Type) (*structInfo, error) { |
1459 | + structMapMutex.RLock() |
1460 | + sinfo, found := structMap[st] |
1461 | + structMapMutex.RUnlock() |
1462 | + if found { |
1463 | + return sinfo, nil |
1464 | + } |
1465 | + n := st.NumField() |
1466 | + fieldsMap := make(map[string]fieldInfo) |
1467 | + fieldsList := make([]fieldInfo, 0, n) |
1468 | + for i := 0; i != n; i++ { |
1469 | + field := st.Field(i) |
1470 | + if field.PkgPath != "" { |
1471 | + continue // Private field |
1472 | + } |
1473 | + |
1474 | + info := fieldInfo{Num: i} |
1475 | + |
1476 | + tag := field.Tag.Get("bson") |
1477 | + if tag == "" && strings.Index(string(field.Tag), ":") < 0 { |
1478 | + tag = string(field.Tag) |
1479 | + } |
1480 | + if tag == "-" { |
1481 | + continue |
1482 | + } |
1483 | + |
1484 | + // XXX Drop this after a few releases. |
1485 | + if s := strings.Index(tag, "/"); s >= 0 { |
1486 | + recommend := tag[:s] |
1487 | + for _, c := range tag[s+1:] { |
1488 | + switch c { |
1489 | + case 'c': |
1490 | + recommend += ",omitempty" |
1491 | + case 's': |
1492 | + recommend += ",minsize" |
1493 | + default: |
1494 | + msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", string([]byte{uint8(c)}), tag, st) |
1495 | + panic(externalPanic(msg)) |
1496 | + } |
1497 | + } |
1498 | + msg := fmt.Sprintf("Replace tag %q in field %s of type %s by %q", tag, field.Name, st, recommend) |
1499 | + panic(externalPanic(msg)) |
1500 | + } |
1501 | + |
1502 | + inline := false |
1503 | + fields := strings.Split(tag, ",") |
1504 | + if len(fields) > 1 { |
1505 | + for _, flag := range fields[1:] { |
1506 | + switch flag { |
1507 | + case "omitempty": |
1508 | + info.OmitEmpty = true |
1509 | + case "minsize": |
1510 | + info.MinSize = true |
1511 | + case "inline": |
1512 | + inline = true |
1513 | + default: |
1514 | + msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st) |
1515 | + panic(externalPanic(msg)) |
1516 | + } |
1517 | + } |
1518 | + tag = fields[0] |
1519 | + } |
1520 | + |
1521 | + if inline { |
1522 | + if field.Type.Kind() != reflect.Struct { |
1523 | + panic("Option ,inline needs a struct value field") |
1524 | + } |
1525 | + sinfo, err := getStructInfo(field.Type) |
1526 | + if err != nil { |
1527 | + return nil, err |
1528 | + } |
1529 | + for _, finfo := range sinfo.FieldsList { |
1530 | + if _, found := fieldsMap[finfo.Key]; found { |
1531 | + msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String() |
1532 | + return nil, errors.New(msg) |
1533 | + } |
1534 | + if finfo.Inline == nil { |
1535 | + finfo.Inline = []int{i, finfo.Num} |
1536 | + } else { |
1537 | + finfo.Inline = append([]int{i}, finfo.Inline...) |
1538 | + } |
1539 | + fieldsMap[finfo.Key] = finfo |
1540 | + fieldsList = append(fieldsList, finfo) |
1541 | + } |
1542 | + continue |
1543 | + } |
1544 | + |
1545 | + if tag != "" { |
1546 | + info.Key = tag |
1547 | + } else { |
1548 | + info.Key = strings.ToLower(field.Name) |
1549 | + } |
1550 | + |
1551 | + if _, found = fieldsMap[info.Key]; found { |
1552 | + msg := "Duplicated key '" + info.Key + "' in struct " + st.String() |
1553 | + return nil, errors.New(msg) |
1554 | + } |
1555 | + |
1556 | + fieldsList = append(fieldsList, info) |
1557 | + fieldsMap[info.Key] = info |
1558 | + } |
1559 | + sinfo = &structInfo{ |
1560 | + fieldsMap, |
1561 | + fieldsList[:len(fieldsMap)], |
1562 | + reflect.New(st).Elem(), |
1563 | + } |
1564 | + structMapMutex.Lock() |
1565 | + structMap[st] = sinfo |
1566 | + structMapMutex.Unlock() |
1567 | + return sinfo, nil |
1568 | +} |
1569 | |
1570 | === added file 'bson/bson_test.go' |
1571 | --- bson/bson_test.go 1970-01-01 00:00:00 +0000 |
1572 | +++ bson/bson_test.go 2013-04-03 09:16:27 +0000 |
1573 | @@ -0,0 +1,1369 @@ |
1574 | +// BSON library for Go |
1575 | +// |
1576 | +// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
1577 | +// |
1578 | +// All rights reserved. |
1579 | +// |
1580 | +// Redistribution and use in source and binary forms, with or without |
1581 | +// modification, are permitted provided that the following conditions are met: |
1582 | +// |
1583 | +// 1. Redistributions of source code must retain the above copyright notice, this |
1584 | +// list of conditions and the following disclaimer. |
1585 | +// 2. Redistributions in binary form must reproduce the above copyright notice, |
1586 | +// this list of conditions and the following disclaimer in the documentation |
1587 | +// and/or other materials provided with the distribution. |
1588 | +// |
1589 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
1590 | +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
1591 | +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
1592 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
1593 | +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
1594 | +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
1595 | +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
1596 | +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
1597 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
1598 | +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
1599 | +// gobson - BSON library for Go. |
1600 | + |
1601 | +package bson_test |
1602 | + |
1603 | +import ( |
1604 | + "encoding/binary" |
1605 | + "encoding/json" |
1606 | + "errors" |
1607 | + . "launchpad.net/gocheck" |
1608 | + "labix.org/v2/mgo/bson" |
1609 | + "net/url" |
1610 | + "reflect" |
1611 | + "testing" |
1612 | + "time" |
1613 | +) |
1614 | + |
1615 | +func TestAll(t *testing.T) { |
1616 | + TestingT(t) |
1617 | +} |
1618 | + |
1619 | +type S struct{} |
1620 | + |
1621 | +var _ = Suite(&S{}) |
1622 | + |
1623 | +// Wrap up the document elements contained in data, prepending the int32 |
1624 | +// length of the data, and appending the '\x00' value closing the document. |
1625 | +func wrapInDoc(data string) string { |
1626 | + result := make([]byte, len(data)+5) |
1627 | + binary.LittleEndian.PutUint32(result, uint32(len(result))) |
1628 | + copy(result[4:], []byte(data)) |
1629 | + return string(result) |
1630 | +} |
1631 | + |
1632 | +func makeZeroDoc(value interface{}) (zero interface{}) { |
1633 | + v := reflect.ValueOf(value) |
1634 | + t := v.Type() |
1635 | + switch t.Kind() { |
1636 | + case reflect.Map: |
1637 | + mv := reflect.MakeMap(t) |
1638 | + zero = mv.Interface() |
1639 | + case reflect.Ptr: |
1640 | + pv := reflect.New(v.Type().Elem()) |
1641 | + zero = pv.Interface() |
1642 | + case reflect.Slice: |
1643 | + zero = reflect.New(t).Interface() |
1644 | + default: |
1645 | + panic("unsupported doc type") |
1646 | + } |
1647 | + return zero |
1648 | +} |
1649 | + |
1650 | +func testUnmarshal(c *C, data string, obj interface{}) { |
1651 | + zero := makeZeroDoc(obj) |
1652 | + err := bson.Unmarshal([]byte(data), zero) |
1653 | + c.Assert(err, IsNil) |
1654 | + c.Assert(zero, DeepEquals, obj) |
1655 | +} |
1656 | + |
1657 | +type testItemType struct { |
1658 | + obj interface{} |
1659 | + data string |
1660 | +} |
1661 | + |
1662 | +// -------------------------------------------------------------------------- |
1663 | +// Samples from bsonspec.org: |
1664 | + |
1665 | +var sampleItems = []testItemType{ |
1666 | + {bson.M{"hello": "world"}, |
1667 | + "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00"}, |
1668 | + {bson.M{"BSON": []interface{}{"awesome", float64(5.05), 1986}}, |
1669 | + "1\x00\x00\x00\x04BSON\x00&\x00\x00\x00\x020\x00\x08\x00\x00\x00" + |
1670 | + "awesome\x00\x011\x00333333\x14@\x102\x00\xc2\x07\x00\x00\x00\x00"}, |
1671 | +} |
1672 | + |
1673 | +func (s *S) TestMarshalSampleItems(c *C) { |
1674 | + for i, item := range sampleItems { |
1675 | + data, err := bson.Marshal(item.obj) |
1676 | + c.Assert(err, IsNil) |
1677 | + c.Assert(string(data), Equals, item.data, Commentf("Failed on item %d", i)) |
1678 | + } |
1679 | +} |
1680 | + |
1681 | +func (s *S) TestUnmarshalSampleItems(c *C) { |
1682 | + for i, item := range sampleItems { |
1683 | + value := bson.M{} |
1684 | + err := bson.Unmarshal([]byte(item.data), value) |
1685 | + c.Assert(err, IsNil) |
1686 | + c.Assert(value, DeepEquals, item.obj, Commentf("Failed on item %d", i)) |
1687 | + } |
1688 | +} |
1689 | + |
1690 | +// -------------------------------------------------------------------------- |
1691 | +// Every type, ordered by the type flag. These are not wrapped with the |
1692 | +// length and last \x00 from the document. wrapInDoc() computes them. |
1693 | +// Note that all of them should be supported as two-way conversions. |
1694 | + |
1695 | +var allItems = []testItemType{ |
1696 | + {bson.M{}, |
1697 | + ""}, |
1698 | + {bson.M{"_": float64(5.05)}, |
1699 | + "\x01_\x00333333\x14@"}, |
1700 | + {bson.M{"_": "yo"}, |
1701 | + "\x02_\x00\x03\x00\x00\x00yo\x00"}, |
1702 | + {bson.M{"_": bson.M{"a": true}}, |
1703 | + "\x03_\x00\x09\x00\x00\x00\x08a\x00\x01\x00"}, |
1704 | + {bson.M{"_": []interface{}{true, false}}, |
1705 | + "\x04_\x00\r\x00\x00\x00\x080\x00\x01\x081\x00\x00\x00"}, |
1706 | + {bson.M{"_": []byte("yo")}, |
1707 | + "\x05_\x00\x02\x00\x00\x00\x00yo"}, |
1708 | + {bson.M{"_": bson.Binary{0x80, []byte("udef")}}, |
1709 | + "\x05_\x00\x04\x00\x00\x00\x80udef"}, |
1710 | + {bson.M{"_": bson.Undefined}, // Obsolete, but still seen in the wild. |
1711 | + "\x06_\x00"}, |
1712 | + {bson.M{"_": bson.ObjectId("0123456789ab")}, |
1713 | + "\x07_\x000123456789ab"}, |
1714 | + {bson.M{"_": false}, |
1715 | + "\x08_\x00\x00"}, |
1716 | + {bson.M{"_": true}, |
1717 | + "\x08_\x00\x01"}, |
1718 | + {bson.M{"_": time.Unix(0, 258e6)}, // Note the NS <=> MS conversion. |
1719 | + "\x09_\x00\x02\x01\x00\x00\x00\x00\x00\x00"}, |
1720 | + {bson.M{"_": nil}, |
1721 | + "\x0A_\x00"}, |
1722 | + {bson.M{"_": bson.RegEx{"ab", "cd"}}, |
1723 | + "\x0B_\x00ab\x00cd\x00"}, |
1724 | + {bson.M{"_": bson.JavaScript{"code", nil}}, |
1725 | + "\x0D_\x00\x05\x00\x00\x00code\x00"}, |
1726 | + {bson.M{"_": bson.Symbol("sym")}, |
1727 | + "\x0E_\x00\x04\x00\x00\x00sym\x00"}, |
1728 | + {bson.M{"_": bson.JavaScript{"code", bson.M{"": nil}}}, |
1729 | + "\x0F_\x00\x14\x00\x00\x00\x05\x00\x00\x00code\x00" + |
1730 | + "\x07\x00\x00\x00\x0A\x00\x00"}, |
1731 | + {bson.M{"_": 258}, |
1732 | + "\x10_\x00\x02\x01\x00\x00"}, |
1733 | + {bson.M{"_": bson.MongoTimestamp(258)}, |
1734 | + "\x11_\x00\x02\x01\x00\x00\x00\x00\x00\x00"}, |
1735 | + {bson.M{"_": int64(258)}, |
1736 | + "\x12_\x00\x02\x01\x00\x00\x00\x00\x00\x00"}, |
1737 | + {bson.M{"_": int64(258 << 32)}, |
1738 | + "\x12_\x00\x00\x00\x00\x00\x02\x01\x00\x00"}, |
1739 | + {bson.M{"_": bson.MaxKey}, |
1740 | + "\x7F_\x00"}, |
1741 | + {bson.M{"_": bson.MinKey}, |
1742 | + "\xFF_\x00"}, |
1743 | +} |
1744 | + |
1745 | +func (s *S) TestMarshalAllItems(c *C) { |
1746 | + for i, item := range allItems { |
1747 | + data, err := bson.Marshal(item.obj) |
1748 | + c.Assert(err, IsNil) |
1749 | + c.Assert(string(data), Equals, wrapInDoc(item.data), Commentf("Failed on item %d: %#v", i, item)) |
1750 | + } |
1751 | +} |
1752 | + |
1753 | +func (s *S) TestUnmarshalAllItems(c *C) { |
1754 | + for i, item := range allItems { |
1755 | + value := bson.M{} |
1756 | + err := bson.Unmarshal([]byte(wrapInDoc(item.data)), value) |
1757 | + c.Assert(err, IsNil) |
1758 | + c.Assert(value, DeepEquals, item.obj, Commentf("Failed on item %d: %#v", i, item)) |
1759 | + } |
1760 | +} |
1761 | + |
1762 | +func (s *S) TestUnmarshalRawAllItems(c *C) { |
1763 | + for i, item := range allItems { |
1764 | + if len(item.data) == 0 { |
1765 | + continue |
1766 | + } |
1767 | + value := item.obj.(bson.M)["_"] |
1768 | + if value == nil { |
1769 | + continue |
1770 | + } |
1771 | + pv := reflect.New(reflect.ValueOf(value).Type()) |
1772 | + raw := bson.Raw{item.data[0], []byte(item.data[3:])} |
1773 | + c.Logf("Unmarshal raw: %#v, %#v", raw, pv.Interface()) |
1774 | + err := raw.Unmarshal(pv.Interface()) |
1775 | + c.Assert(err, IsNil) |
1776 | + c.Assert(pv.Elem().Interface(), DeepEquals, value, Commentf("Failed on item %d: %#v", i, item)) |
1777 | + } |
1778 | +} |
1779 | + |
1780 | +func (s *S) TestUnmarshalRawIncompatible(c *C) { |
1781 | + raw := bson.Raw{0x08, []byte{0x01}} // true |
1782 | + err := raw.Unmarshal(&struct{}{}) |
1783 | + c.Assert(err, ErrorMatches, "BSON kind 0x08 isn't compatible with type struct \\{\\}") |
1784 | +} |
1785 | + |
1786 | +func (s *S) TestUnmarshalZeroesStruct(c *C) { |
1787 | + data, err := bson.Marshal(bson.M{"b": 2}) |
1788 | + c.Assert(err, IsNil) |
1789 | + type T struct{ A, B int } |
1790 | + v := T{A: 1} |
1791 | + err = bson.Unmarshal(data, &v) |
1792 | + c.Assert(err, IsNil) |
1793 | + c.Assert(v.A, Equals, 0) |
1794 | + c.Assert(v.B, Equals, 2) |
1795 | +} |
1796 | + |
1797 | +func (s *S) TestUnmarshalZeroesMap(c *C) { |
1798 | + data, err := bson.Marshal(bson.M{"b": 2}) |
1799 | + c.Assert(err, IsNil) |
1800 | + m := bson.M{"a": 1} |
1801 | + err = bson.Unmarshal(data, &m) |
1802 | + c.Assert(err, IsNil) |
1803 | + c.Assert(m, DeepEquals, bson.M{"b": 2}) |
1804 | +} |
1805 | + |
1806 | +func (s *S) TestUnmarshalNonNilInterface(c *C) { |
1807 | + data, err := bson.Marshal(bson.M{"b": 2}) |
1808 | + c.Assert(err, IsNil) |
1809 | + m := bson.M{"a": 1} |
1810 | + var i interface{} |
1811 | + i = m |
1812 | + err = bson.Unmarshal(data, &i) |
1813 | + c.Assert(err, IsNil) |
1814 | + c.Assert(i, DeepEquals, bson.M{"b": 2}) |
1815 | + c.Assert(m, DeepEquals, bson.M{"a": 1}) |
1816 | +} |
1817 | + |
1818 | +// -------------------------------------------------------------------------- |
1819 | +// Some one way marshaling operations which would unmarshal differently. |
1820 | + |
1821 | +var oneWayMarshalItems = []testItemType{ |
1822 | + // These are being passed as pointers, and will unmarshal as values. |
1823 | + {bson.M{"": &bson.Binary{0x02, []byte("old")}}, |
1824 | + "\x05\x00\x07\x00\x00\x00\x02\x03\x00\x00\x00old"}, |
1825 | + {bson.M{"": &bson.Binary{0x80, []byte("udef")}}, |
1826 | + "\x05\x00\x04\x00\x00\x00\x80udef"}, |
1827 | + {bson.M{"": &bson.RegEx{"ab", "cd"}}, |
1828 | + "\x0B\x00ab\x00cd\x00"}, |
1829 | + {bson.M{"": &bson.JavaScript{"code", nil}}, |
1830 | + "\x0D\x00\x05\x00\x00\x00code\x00"}, |
1831 | + {bson.M{"": &bson.JavaScript{"code", bson.M{"": nil}}}, |
1832 | + "\x0F\x00\x14\x00\x00\x00\x05\x00\x00\x00code\x00" + |
1833 | + "\x07\x00\x00\x00\x0A\x00\x00"}, |
1834 | + |
1835 | + // There's no float32 type in BSON. Will encode as a float64. |
1836 | + {bson.M{"": float32(5.05)}, |
1837 | + "\x01\x00\x00\x00\x00@33\x14@"}, |
1838 | + |
1839 | + // The array will be unmarshaled as a slice instead. |
1840 | + {bson.M{"": [2]bool{true, false}}, |
1841 | + "\x04\x00\r\x00\x00\x00\x080\x00\x01\x081\x00\x00\x00"}, |
1842 | + |
1843 | + // The typed slice will be unmarshaled as []interface{}. |
1844 | + {bson.M{"": []bool{true, false}}, |
1845 | + "\x04\x00\r\x00\x00\x00\x080\x00\x01\x081\x00\x00\x00"}, |
1846 | + |
1847 | + // Will unmarshal as a []byte. |
1848 | + {bson.M{"": bson.Binary{0x00, []byte("yo")}}, |
1849 | + "\x05\x00\x02\x00\x00\x00\x00yo"}, |
1850 | + {bson.M{"": bson.Binary{0x02, []byte("old")}}, |
1851 | + "\x05\x00\x07\x00\x00\x00\x02\x03\x00\x00\x00old"}, |
1852 | + |
1853 | + // No way to preserve the type information here. We might encode as a zero |
1854 | + // value, but this would mean that pointer values in structs wouldn't be |
1855 | + // able to correctly distinguish between unset and set to the zero value. |
1856 | + {bson.M{"": (*byte)(nil)}, |
1857 | + "\x0A\x00"}, |
1858 | + |
1859 | + // No int types smaller than int32 in BSON. Could encode this as a char, |
1860 | + // but it would still be ambiguous, take more, and be awkward in Go when |
1861 | + // loaded without typing information. |
1862 | + {bson.M{"": byte(8)}, |
1863 | + "\x10\x00\x08\x00\x00\x00"}, |
1864 | + |
1865 | + // There are no unsigned types in BSON. Will unmarshal as int32 or int64. |
1866 | + {bson.M{"": uint32(258)}, |
1867 | + "\x10\x00\x02\x01\x00\x00"}, |
1868 | + {bson.M{"": uint64(258)}, |
1869 | + "\x12\x00\x02\x01\x00\x00\x00\x00\x00\x00"}, |
1870 | + {bson.M{"": uint64(258 << 32)}, |
1871 | + "\x12\x00\x00\x00\x00\x00\x02\x01\x00\x00"}, |
1872 | + |
1873 | + // This will unmarshal as int. |
1874 | + {bson.M{"": int32(258)}, |
1875 | + "\x10\x00\x02\x01\x00\x00"}, |
1876 | + |
1877 | + // That's a special case. The unsigned value is too large for an int32, |
1878 | + // so an int64 is used instead. |
1879 | + {bson.M{"": uint32(1<<32 - 1)}, |
1880 | + "\x12\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00"}, |
1881 | + {bson.M{"": uint(1<<32 - 1)}, |
1882 | + "\x12\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00"}, |
1883 | +} |
1884 | + |
1885 | +func (s *S) TestOneWayMarshalItems(c *C) { |
1886 | + for i, item := range oneWayMarshalItems { |
1887 | + data, err := bson.Marshal(item.obj) |
1888 | + c.Assert(err, IsNil) |
1889 | + c.Assert(string(data), Equals, wrapInDoc(item.data), |
1890 | + Commentf("Failed on item %d", i)) |
1891 | + } |
1892 | +} |
1893 | + |
1894 | +// -------------------------------------------------------------------------- |
1895 | +// Two-way tests for user-defined structures using the samples |
1896 | +// from bsonspec.org. |
1897 | + |
1898 | +type specSample1 struct { |
1899 | + Hello string |
1900 | +} |
1901 | + |
1902 | +type specSample2 struct { |
1903 | + BSON []interface{} "BSON" |
1904 | +} |
1905 | + |
1906 | +var structSampleItems = []testItemType{ |
1907 | + {&specSample1{"world"}, |
1908 | + "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00"}, |
1909 | + {&specSample2{[]interface{}{"awesome", float64(5.05), 1986}}, |
1910 | + "1\x00\x00\x00\x04BSON\x00&\x00\x00\x00\x020\x00\x08\x00\x00\x00" + |
1911 | + "awesome\x00\x011\x00333333\x14@\x102\x00\xc2\x07\x00\x00\x00\x00"}, |
1912 | +} |
1913 | + |
1914 | +func (s *S) TestMarshalStructSampleItems(c *C) { |
1915 | + for i, item := range structSampleItems { |
1916 | + data, err := bson.Marshal(item.obj) |
1917 | + c.Assert(err, IsNil) |
1918 | + c.Assert(string(data), Equals, item.data, |
1919 | + Commentf("Failed on item %d", i)) |
1920 | + } |
1921 | +} |
1922 | + |
1923 | +func (s *S) TestUnmarshalStructSampleItems(c *C) { |
1924 | + for _, item := range structSampleItems { |
1925 | + testUnmarshal(c, item.data, item.obj) |
1926 | + } |
1927 | +} |
1928 | + |
1929 | +// -------------------------------------------------------------------------- |
1930 | +// Generic two-way struct marshaling tests. |
1931 | + |
1932 | +var bytevar = byte(8) |
1933 | +var byteptr = &bytevar |
1934 | + |
1935 | +var structItems = []testItemType{ |
1936 | + {&struct{ Ptr *byte }{nil}, |
1937 | + "\x0Aptr\x00"}, |
1938 | + {&struct{ Ptr *byte }{&bytevar}, |
1939 | + "\x10ptr\x00\x08\x00\x00\x00"}, |
1940 | + {&struct{ Ptr **byte }{&byteptr}, |
1941 | + "\x10ptr\x00\x08\x00\x00\x00"}, |
1942 | + {&struct{ Byte byte }{8}, |
1943 | + "\x10byte\x00\x08\x00\x00\x00"}, |
1944 | + {&struct{ Byte byte }{0}, |
1945 | + "\x10byte\x00\x00\x00\x00\x00"}, |
1946 | + {&struct { |
1947 | + V byte "Tag" |
1948 | + }{8}, |
1949 | + "\x10Tag\x00\x08\x00\x00\x00"}, |
1950 | + {&struct { |
1951 | + V *struct { |
1952 | + Byte byte |
1953 | + } |
1954 | + }{&struct{ Byte byte }{8}}, |
1955 | + "\x03v\x00" + "\x0f\x00\x00\x00\x10byte\x00\b\x00\x00\x00\x00"}, |
1956 | + {&struct{ priv byte }{}, ""}, |
1957 | + |
1958 | + // The order of the dumped fields should be the same in the struct. |
1959 | + {&struct{ A, C, B, D, F, E *byte }{}, |
1960 | + "\x0Aa\x00\x0Ac\x00\x0Ab\x00\x0Ad\x00\x0Af\x00\x0Ae\x00"}, |
1961 | + |
1962 | + {&struct{ V bson.Raw }{bson.Raw{0x03, []byte("\x0f\x00\x00\x00\x10byte\x00\b\x00\x00\x00\x00")}}, |
1963 | + "\x03v\x00" + "\x0f\x00\x00\x00\x10byte\x00\b\x00\x00\x00\x00"}, |
1964 | + {&struct{ V bson.Raw }{bson.Raw{0x10, []byte("\x00\x00\x00\x00")}}, |
1965 | + "\x10v\x00" + "\x00\x00\x00\x00"}, |
1966 | + |
1967 | + // Byte arrays. |
1968 | + {&struct{ V [2]byte }{[2]byte{'y', 'o'}}, |
1969 | + "\x05v\x00\x02\x00\x00\x00\x00yo"}, |
1970 | +} |
1971 | + |
1972 | +func (s *S) TestMarshalStructItems(c *C) { |
1973 | + for i, item := range structItems { |
1974 | + data, err := bson.Marshal(item.obj) |
1975 | + c.Assert(err, IsNil) |
1976 | + c.Assert(string(data), Equals, wrapInDoc(item.data), |
1977 | + Commentf("Failed on item %d", i)) |
1978 | + } |
1979 | +} |
1980 | + |
1981 | +func (s *S) TestUnmarshalStructItems(c *C) { |
1982 | + for _, item := range structItems { |
1983 | + testUnmarshal(c, wrapInDoc(item.data), item.obj) |
1984 | + } |
1985 | +} |
1986 | + |
1987 | +func (s *S) TestUnmarshalRawStructItems(c *C) { |
1988 | + for i, item := range structItems { |
1989 | + raw := bson.Raw{0x03, []byte(wrapInDoc(item.data))} |
1990 | + zero := makeZeroDoc(item.obj) |
1991 | + err := raw.Unmarshal(zero) |
1992 | + c.Assert(err, IsNil) |
1993 | + c.Assert(zero, DeepEquals, item.obj, Commentf("Failed on item %d: %#v", i, item)) |
1994 | + } |
1995 | +} |
1996 | + |
1997 | +func (s *S) TestUnmarshalRawNil(c *C) { |
1998 | + // Regression test: shouldn't try to nil out the pointer itself, |
1999 | + // as it's not settable. |
2000 | + raw := bson.Raw{0x0A, []byte{}} |
2001 | + err := raw.Unmarshal(&struct{}{}) |
2002 | + c.Assert(err, IsNil) |
2003 | +} |
2004 | + |
2005 | +// -------------------------------------------------------------------------- |
2006 | +// One-way marshaling tests. |
2007 | + |
2008 | +type dOnIface struct { |
2009 | + D interface{} |
2010 | +} |
2011 | + |
2012 | +type ignoreField struct { |
2013 | + Before string |
2014 | + Ignore string `bson:"-"` |
2015 | + After string |
2016 | +} |
2017 | + |
2018 | +var marshalItems = []testItemType{ |
2019 | + // Ordered document dump. Will unmarshal as a dictionary by default. |
2020 | + {bson.D{{"a", nil}, {"c", nil}, {"b", nil}, {"d", nil}, {"f", nil}, {"e", true}}, |
2021 | + "\x0Aa\x00\x0Ac\x00\x0Ab\x00\x0Ad\x00\x0Af\x00\x08e\x00\x01"}, |
2022 | + {MyD{{"a", nil}, {"c", nil}, {"b", nil}, {"d", nil}, {"f", nil}, {"e", true}}, |
2023 | + "\x0Aa\x00\x0Ac\x00\x0Ab\x00\x0Ad\x00\x0Af\x00\x08e\x00\x01"}, |
2024 | + {&dOnIface{bson.D{{"a", nil}, {"c", nil}, {"b", nil}, {"d", true}}}, |
2025 | + "\x03d\x00" + wrapInDoc("\x0Aa\x00\x0Ac\x00\x0Ab\x00\x08d\x00\x01")}, |
2026 | + {&ignoreField{"before", "ignore", "after"}, |
2027 | + "\x02before\x00\a\x00\x00\x00before\x00\x02after\x00\x06\x00\x00\x00after\x00"}, |
2028 | + |
2029 | + // Marshalling a Raw document does nothing. |
2030 | + {bson.Raw{0x03, []byte(wrapInDoc("anything"))}, |
2031 | + "anything"}, |
2032 | + {bson.Raw{Data: []byte(wrapInDoc("anything"))}, |
2033 | + "anything"}, |
2034 | +} |
2035 | + |
2036 | +func (s *S) TestMarshalOneWayItems(c *C) { |
2037 | + for _, item := range marshalItems { |
2038 | + data, err := bson.Marshal(item.obj) |
2039 | + c.Assert(err, IsNil) |
2040 | + c.Assert(string(data), Equals, wrapInDoc(item.data)) |
2041 | + } |
2042 | +} |
2043 | + |
2044 | +// -------------------------------------------------------------------------- |
2045 | +// One-way unmarshaling tests. |
2046 | + |
2047 | +var unmarshalItems = []testItemType{ |
2048 | + // Field is private. Should not attempt to unmarshal it. |
2049 | + {&struct{ priv byte }{}, |
2050 | + "\x10priv\x00\x08\x00\x00\x00"}, |
2051 | + |
2052 | + // Wrong casing. Field names are lowercased. |
2053 | + {&struct{ Byte byte }{}, |
2054 | + "\x10Byte\x00\x08\x00\x00\x00"}, |
2055 | + |
2056 | + // Ignore non-existing field. |
2057 | + {&struct{ Byte byte }{9}, |
2058 | + "\x10boot\x00\x08\x00\x00\x00" + "\x10byte\x00\x09\x00\x00\x00"}, |
2059 | + |
2060 | + // Do not unmarshal on ignored field. |
2061 | + {&ignoreField{"before", "", "after"}, |
2062 | + "\x02before\x00\a\x00\x00\x00before\x00" + |
2063 | + "\x02-\x00\a\x00\x00\x00ignore\x00" + |
2064 | + "\x02after\x00\x06\x00\x00\x00after\x00"}, |
2065 | + |
2066 | + // Ignore unsuitable types silently. |
2067 | + {map[string]string{"str": "s"}, |
2068 | + "\x02str\x00\x02\x00\x00\x00s\x00" + "\x10int\x00\x01\x00\x00\x00"}, |
2069 | + {map[string][]int{"array": []int{5, 9}}, |
2070 | + "\x04array\x00" + wrapInDoc("\x100\x00\x05\x00\x00\x00"+"\x021\x00\x02\x00\x00\x00s\x00"+"\x102\x00\x09\x00\x00\x00")}, |
2071 | + |
2072 | + // Wrong type. Shouldn't init pointer. |
2073 | + {&struct{ Str *byte }{}, |
2074 | + "\x02str\x00\x02\x00\x00\x00s\x00"}, |
2075 | + {&struct{ Str *struct{ Str string } }{}, |
2076 | + "\x02str\x00\x02\x00\x00\x00s\x00"}, |
2077 | + |
2078 | + // Ordered document. |
2079 | + {&struct{ bson.D }{bson.D{{"a", nil}, {"c", nil}, {"b", nil}, {"d", true}}}, |
2080 | + "\x03d\x00" + wrapInDoc("\x0Aa\x00\x0Ac\x00\x0Ab\x00\x08d\x00\x01")}, |
2081 | + |
2082 | + // Raw document. |
2083 | + {&bson.Raw{0x03, []byte(wrapInDoc("\x10byte\x00\x08\x00\x00\x00"))}, |
2084 | + "\x10byte\x00\x08\x00\x00\x00"}, |
2085 | + |
2086 | + // Decode old binary. |
2087 | + {bson.M{"_": []byte("old")}, |
2088 | + "\x05_\x00\x07\x00\x00\x00\x02\x03\x00\x00\x00old"}, |
2089 | +} |
2090 | + |
2091 | +func (s *S) TestUnmarshalOneWayItems(c *C) { |
2092 | + for _, item := range unmarshalItems { |
2093 | + testUnmarshal(c, wrapInDoc(item.data), item.obj) |
2094 | + } |
2095 | +} |
2096 | + |
2097 | +func (s *S) TestUnmarshalNilInStruct(c *C) { |
2098 | + // Nil is the default value, so we need to ensure it's indeed being set. |
2099 | + b := byte(1) |
2100 | + v := &struct{ Ptr *byte }{&b} |
2101 | + err := bson.Unmarshal([]byte(wrapInDoc("\x0Aptr\x00")), v) |
2102 | + c.Assert(err, IsNil) |
2103 | + c.Assert(v, DeepEquals, &struct{ Ptr *byte }{nil}) |
2104 | +} |
2105 | + |
2106 | +// -------------------------------------------------------------------------- |
2107 | +// Marshalling error cases. |
2108 | + |
2109 | +type structWithDupKeys struct { |
2110 | + Name byte |
2111 | + Other byte "name" // Tag should precede. |
2112 | +} |
2113 | + |
2114 | +var marshalErrorItems = []testItemType{ |
2115 | + {bson.M{"": uint64(1 << 63)}, |
2116 | + "BSON has no uint64 type, and value is too large to fit correctly in an int64"}, |
2117 | + {bson.M{"": bson.ObjectId("tooshort")}, |
2118 | + "ObjectIDs must be exactly 12 bytes long \\(got 8\\)"}, |
2119 | + {int64(123), |
2120 | + "Can't marshal int64 as a BSON document"}, |
2121 | + {bson.M{"": 1i}, |
2122 | + "Can't marshal complex128 in a BSON document"}, |
2123 | + {&structWithDupKeys{}, |
2124 | + "Duplicated key 'name' in struct bson_test.structWithDupKeys"}, |
2125 | + {bson.Raw{0x0A, []byte{}}, |
2126 | + "Attempted to unmarshal Raw kind 10 as a document"}, |
2127 | + {&inlineCantPtr{&struct{ A, B int }{1, 2}}, |
2128 | + "Option ,inline needs a struct value field"}, |
2129 | + {&inlineDupName{1, struct{ A, B int }{2, 3}}, |
2130 | + "Duplicated key 'a' in struct bson_test.inlineDupName"}, |
2131 | +} |
2132 | + |
2133 | +func (s *S) TestMarshalErrorItems(c *C) { |
2134 | + for _, item := range marshalErrorItems { |
2135 | + data, err := bson.Marshal(item.obj) |
2136 | + c.Assert(err, ErrorMatches, item.data) |
2137 | + c.Assert(data, IsNil) |
2138 | + } |
2139 | +} |
2140 | + |
2141 | +// -------------------------------------------------------------------------- |
2142 | +// Unmarshalling error cases. |
2143 | + |
2144 | +type unmarshalErrorType struct { |
2145 | + obj interface{} |
2146 | + data string |
2147 | + error string |
2148 | +} |
2149 | + |
2150 | +var unmarshalErrorItems = []unmarshalErrorType{ |
2151 | + // Tag name conflicts with existing parameter. |
2152 | + {&structWithDupKeys{}, |
2153 | + "\x10name\x00\x08\x00\x00\x00", |
2154 | + "Duplicated key 'name' in struct bson_test.structWithDupKeys"}, |
2155 | + |
2156 | + // Non-string map key. |
2157 | + {map[int]interface{}{}, |
2158 | + "\x10name\x00\x08\x00\x00\x00", |
2159 | + "BSON map must have string keys. Got: map\\[int\\]interface \\{\\}"}, |
2160 | + |
2161 | + {nil, |
2162 | + "\xEEname\x00", |
2163 | + "Unknown element kind \\(0xEE\\)"}, |
2164 | + |
2165 | + {struct{ Name bool }{}, |
2166 | + "\x10name\x00\x08\x00\x00\x00", |
2167 | + "Unmarshal can't deal with struct values. Use a pointer."}, |
2168 | + |
2169 | + {123, |
2170 | + "\x10name\x00\x08\x00\x00\x00", |
2171 | + "Unmarshal needs a map or a pointer to a struct."}, |
2172 | +} |
2173 | + |
2174 | +func (s *S) TestUnmarshalErrorItems(c *C) { |
2175 | + for _, item := range unmarshalErrorItems { |
2176 | + data := []byte(wrapInDoc(item.data)) |
2177 | + var value interface{} |
2178 | + switch reflect.ValueOf(item.obj).Kind() { |
2179 | + case reflect.Map, reflect.Ptr: |
2180 | + value = makeZeroDoc(item.obj) |
2181 | + case reflect.Invalid: |
2182 | + value = bson.M{} |
2183 | + default: |
2184 | + value = item.obj |
2185 | + } |
2186 | + err := bson.Unmarshal(data, value) |
2187 | + c.Assert(err, ErrorMatches, item.error) |
2188 | + } |
2189 | +} |
2190 | + |
2191 | +type unmarshalRawErrorType struct { |
2192 | + obj interface{} |
2193 | + raw bson.Raw |
2194 | + error string |
2195 | +} |
2196 | + |
2197 | +var unmarshalRawErrorItems = []unmarshalRawErrorType{ |
2198 | + // Tag name conflicts with existing parameter. |
2199 | + {&structWithDupKeys{}, |
2200 | + bson.Raw{0x03, []byte("\x10byte\x00\x08\x00\x00\x00")}, |
2201 | + "Duplicated key 'name' in struct bson_test.structWithDupKeys"}, |
2202 | + |
2203 | + {&struct{}{}, |
2204 | + bson.Raw{0xEE, []byte{}}, |
2205 | + "Unknown element kind \\(0xEE\\)"}, |
2206 | + |
2207 | + {struct{ Name bool }{}, |
2208 | + bson.Raw{0x10, []byte("\x08\x00\x00\x00")}, |
2209 | + "Raw Unmarshal can't deal with struct values. Use a pointer."}, |
2210 | + |
2211 | + {123, |
2212 | + bson.Raw{0x10, []byte("\x08\x00\x00\x00")}, |
2213 | + "Raw Unmarshal needs a map or a valid pointer."}, |
2214 | +} |
2215 | + |
2216 | +func (s *S) TestUnmarshalRawErrorItems(c *C) { |
2217 | + for i, item := range unmarshalRawErrorItems { |
2218 | + err := item.raw.Unmarshal(item.obj) |
2219 | + c.Assert(err, ErrorMatches, item.error, Commentf("Failed on item %d: %#v\n", i, item)) |
2220 | + } |
2221 | +} |
2222 | + |
2223 | +var corruptedData = []string{ |
2224 | + "\x04\x00\x00\x00\x00", // Shorter than minimum |
2225 | + "\x06\x00\x00\x00\x00", // Not enough data |
2226 | + "\x05\x00\x00", // Broken length |
2227 | + "\x05\x00\x00\x00\xff", // Corrupted termination |
2228 | + "\x0A\x00\x00\x00\x0Aooop\x00", // Unfinished C string |
2229 | + |
2230 | + // Array end past end of string (s[2]=0x07 is correct) |
2231 | + wrapInDoc("\x04\x00\x09\x00\x00\x00\x0A\x00\x00"), |
2232 | + |
2233 | + // Array end within string, but past acceptable. |
2234 | + wrapInDoc("\x04\x00\x08\x00\x00\x00\x0A\x00\x00"), |
2235 | + |
2236 | + // Document end within string, but past acceptable. |
2237 | + wrapInDoc("\x03\x00\x08\x00\x00\x00\x0A\x00\x00"), |
2238 | + |
2239 | + // String with corrupted end. |
2240 | + wrapInDoc("\x02\x00\x03\x00\x00\x00yo\xFF"), |
2241 | +} |
2242 | + |
2243 | +func (s *S) TestUnmarshalMapDocumentTooShort(c *C) { |
2244 | + for _, data := range corruptedData { |
2245 | + err := bson.Unmarshal([]byte(data), bson.M{}) |
2246 | + c.Assert(err, ErrorMatches, "Document is corrupted") |
2247 | + |
2248 | + err = bson.Unmarshal([]byte(data), &struct{}{}) |
2249 | + c.Assert(err, ErrorMatches, "Document is corrupted") |
2250 | + } |
2251 | +} |
2252 | + |
2253 | +// -------------------------------------------------------------------------- |
2254 | +// Setter test cases. |
2255 | + |
2256 | +var setterResult = map[string]error{} |
2257 | + |
2258 | +type setterType struct { |
2259 | + received interface{} |
2260 | +} |
2261 | + |
2262 | +func (o *setterType) SetBSON(raw bson.Raw) error { |
2263 | + err := raw.Unmarshal(&o.received) |
2264 | + if err != nil { |
2265 | + panic("The panic:" + err.Error()) |
2266 | + } |
2267 | + if s, ok := o.received.(string); ok { |
2268 | + if result, ok := setterResult[s]; ok { |
2269 | + return result |
2270 | + } |
2271 | + } |
2272 | + return nil |
2273 | +} |
2274 | + |
2275 | +type ptrSetterDoc struct { |
2276 | + Field *setterType "_" |
2277 | +} |
2278 | + |
2279 | +type valSetterDoc struct { |
2280 | + Field setterType "_" |
2281 | +} |
2282 | + |
2283 | +func (s *S) TestUnmarshalAllItemsWithPtrSetter(c *C) { |
2284 | + for _, item := range allItems { |
2285 | + for i := 0; i != 2; i++ { |
2286 | + var field *setterType |
2287 | + if i == 0 { |
2288 | + obj := &ptrSetterDoc{} |
2289 | + err := bson.Unmarshal([]byte(wrapInDoc(item.data)), obj) |
2290 | + c.Assert(err, IsNil) |
2291 | + field = obj.Field |
2292 | + } else { |
2293 | + obj := &valSetterDoc{} |
2294 | + err := bson.Unmarshal([]byte(wrapInDoc(item.data)), obj) |
2295 | + c.Assert(err, IsNil) |
2296 | + field = &obj.Field |
2297 | + } |
2298 | + if item.data == "" { |
2299 | + // Nothing to unmarshal. Should be untouched. |
2300 | + if i == 0 { |
2301 | + c.Assert(field, IsNil) |
2302 | + } else { |
2303 | + c.Assert(field.received, IsNil) |
2304 | + } |
2305 | + } else { |
2306 | + expected := item.obj.(bson.M)["_"] |
2307 | + c.Assert(field, NotNil, Commentf("Pointer not initialized (%#v)", expected)) |
2308 | + c.Assert(field.received, DeepEquals, expected) |
2309 | + } |
2310 | + } |
2311 | + } |
2312 | +} |
2313 | + |
2314 | +func (s *S) TestUnmarshalWholeDocumentWithSetter(c *C) { |
2315 | + obj := &setterType{} |
2316 | + err := bson.Unmarshal([]byte(sampleItems[0].data), obj) |
2317 | + c.Assert(err, IsNil) |
2318 | + c.Assert(obj.received, DeepEquals, bson.M{"hello": "world"}) |
2319 | +} |
2320 | + |
2321 | +func (s *S) TestUnmarshalSetterOmits(c *C) { |
2322 | + setterResult["2"] = &bson.TypeError{} |
2323 | + setterResult["4"] = &bson.TypeError{} |
2324 | + defer func() { |
2325 | + delete(setterResult, "2") |
2326 | + delete(setterResult, "4") |
2327 | + }() |
2328 | + |
2329 | + m := map[string]*setterType{} |
2330 | + data := wrapInDoc("\x02abc\x00\x02\x00\x00\x001\x00" + |
2331 | + "\x02def\x00\x02\x00\x00\x002\x00" + |
2332 | + "\x02ghi\x00\x02\x00\x00\x003\x00" + |
2333 | + "\x02jkl\x00\x02\x00\x00\x004\x00") |
2334 | + err := bson.Unmarshal([]byte(data), m) |
2335 | + c.Assert(err, IsNil) |
2336 | + c.Assert(m["abc"], NotNil) |
2337 | + c.Assert(m["def"], IsNil) |
2338 | + c.Assert(m["ghi"], NotNil) |
2339 | + c.Assert(m["jkl"], IsNil) |
2340 | + |
2341 | + c.Assert(m["abc"].received, Equals, "1") |
2342 | + c.Assert(m["ghi"].received, Equals, "3") |
2343 | +} |
2344 | + |
2345 | +func (s *S) TestUnmarshalSetterErrors(c *C) { |
2346 | + boom := errors.New("BOOM") |
2347 | + setterResult["2"] = boom |
2348 | + defer delete(setterResult, "2") |
2349 | + |
2350 | + m := map[string]*setterType{} |
2351 | + data := wrapInDoc("\x02abc\x00\x02\x00\x00\x001\x00" + |
2352 | + "\x02def\x00\x02\x00\x00\x002\x00" + |
2353 | + "\x02ghi\x00\x02\x00\x00\x003\x00") |
2354 | + err := bson.Unmarshal([]byte(data), m) |
2355 | + c.Assert(err, Equals, boom) |
2356 | + c.Assert(m["abc"], NotNil) |
2357 | + c.Assert(m["def"], IsNil) |
2358 | + c.Assert(m["ghi"], IsNil) |
2359 | + |
2360 | + c.Assert(m["abc"].received, Equals, "1") |
2361 | +} |
2362 | + |
2363 | +func (s *S) TestDMap(c *C) { |
2364 | + d := bson.D{{"a", 1}, {"b", 2}} |
2365 | + c.Assert(d.Map(), DeepEquals, bson.M{"a": 1, "b": 2}) |
2366 | +} |
2367 | + |
2368 | +func (s *S) TestUnmarshalSetterSetZero(c *C) { |
2369 | + setterResult["foo"] = bson.SetZero |
2370 | + defer delete(setterResult, "field") |
2371 | + |
2372 | + data, err := bson.Marshal(bson.M{"field": "foo"}) |
2373 | + c.Assert(err, IsNil) |
2374 | + |
2375 | + m := map[string]*setterType{} |
2376 | + err = bson.Unmarshal([]byte(data), m) |
2377 | + c.Assert(err, IsNil) |
2378 | + |
2379 | + value, ok := m["field"] |
2380 | + c.Assert(ok, Equals, true) |
2381 | + c.Assert(value, IsNil) |
2382 | +} |
2383 | + |
2384 | + |
2385 | +// -------------------------------------------------------------------------- |
2386 | +// Getter test cases. |
2387 | + |
2388 | +type typeWithGetter struct { |
2389 | + result interface{} |
2390 | + err error |
2391 | +} |
2392 | + |
2393 | +func (t *typeWithGetter) GetBSON() (interface{}, error) { |
2394 | + return t.result, t.err |
2395 | +} |
2396 | + |
2397 | +type docWithGetterField struct { |
2398 | + Field *typeWithGetter "_" |
2399 | +} |
2400 | + |
2401 | +func (s *S) TestMarshalAllItemsWithGetter(c *C) { |
2402 | + for i, item := range allItems { |
2403 | + if item.data == "" { |
2404 | + continue |
2405 | + } |
2406 | + obj := &docWithGetterField{} |
2407 | + obj.Field = &typeWithGetter{result: item.obj.(bson.M)["_"]} |
2408 | + data, err := bson.Marshal(obj) |
2409 | + c.Assert(err, IsNil) |
2410 | + c.Assert(string(data), Equals, wrapInDoc(item.data), |
2411 | + Commentf("Failed on item #%d", i)) |
2412 | + } |
2413 | +} |
2414 | + |
2415 | +func (s *S) TestMarshalWholeDocumentWithGetter(c *C) { |
2416 | + obj := &typeWithGetter{result: sampleItems[0].obj} |
2417 | + data, err := bson.Marshal(obj) |
2418 | + c.Assert(err, IsNil) |
2419 | + c.Assert(string(data), Equals, sampleItems[0].data) |
2420 | +} |
2421 | + |
2422 | +func (s *S) TestGetterErrors(c *C) { |
2423 | + e := errors.New("oops") |
2424 | + |
2425 | + obj1 := &docWithGetterField{} |
2426 | + obj1.Field = &typeWithGetter{sampleItems[0].obj, e} |
2427 | + data, err := bson.Marshal(obj1) |
2428 | + c.Assert(err, ErrorMatches, "oops") |
2429 | + c.Assert(data, IsNil) |
2430 | + |
2431 | + obj2 := &typeWithGetter{sampleItems[0].obj, e} |
2432 | + data, err = bson.Marshal(obj2) |
2433 | + c.Assert(err, ErrorMatches, "oops") |
2434 | + c.Assert(data, IsNil) |
2435 | +} |
2436 | + |
2437 | +type intGetter int64 |
2438 | + |
2439 | +func (t intGetter) GetBSON() (interface{}, error) { |
2440 | + return int64(t), nil |
2441 | +} |
2442 | + |
2443 | +type typeWithIntGetter struct { |
2444 | + V intGetter ",minsize" |
2445 | +} |
2446 | + |
2447 | +func (s *S) TestMarshalShortWithGetter(c *C) { |
2448 | + obj := typeWithIntGetter{42} |
2449 | + data, err := bson.Marshal(obj) |
2450 | + c.Assert(err, IsNil) |
2451 | + m := bson.M{} |
2452 | + err = bson.Unmarshal(data, m) |
2453 | + c.Assert(m["v"], Equals, 42) |
2454 | +} |
2455 | + |
2456 | +// -------------------------------------------------------------------------- |
2457 | +// Cross-type conversion tests. |
2458 | + |
2459 | +type crossTypeItem struct { |
2460 | + obj1 interface{} |
2461 | + obj2 interface{} |
2462 | +} |
2463 | + |
2464 | +type condStr struct { |
2465 | + V string ",omitempty" |
2466 | +} |
2467 | +type condStrNS struct { |
2468 | + V string `a:"A" bson:",omitempty" b:"B"` |
2469 | +} |
2470 | +type condBool struct { |
2471 | + V bool ",omitempty" |
2472 | +} |
2473 | +type condInt struct { |
2474 | + V int ",omitempty" |
2475 | +} |
2476 | +type condUInt struct { |
2477 | + V uint ",omitempty" |
2478 | +} |
2479 | +type condFloat struct { |
2480 | + V float64 ",omitempty" |
2481 | +} |
2482 | +type condIface struct { |
2483 | + V interface{} ",omitempty" |
2484 | +} |
2485 | +type condPtr struct { |
2486 | + V *bool ",omitempty" |
2487 | +} |
2488 | +type condSlice struct { |
2489 | + V []string ",omitempty" |
2490 | +} |
2491 | +type condMap struct { |
2492 | + V map[string]int ",omitempty" |
2493 | +} |
2494 | +type namedCondStr struct { |
2495 | + V string "myv,omitempty" |
2496 | +} |
2497 | +type condTime struct { |
2498 | + V time.Time ",omitempty" |
2499 | +} |
2500 | + |
2501 | +type shortInt struct { |
2502 | + V int64 ",minsize" |
2503 | +} |
2504 | +type shortUint struct { |
2505 | + V uint64 ",minsize" |
2506 | +} |
2507 | +type shortIface struct { |
2508 | + V interface{} ",minsize" |
2509 | +} |
2510 | +type shortPtr struct { |
2511 | + V *int64 ",minsize" |
2512 | +} |
2513 | +type shortNonEmptyInt struct { |
2514 | + V int64 ",minsize,omitempty" |
2515 | +} |
2516 | + |
2517 | +type inlineInt struct { |
2518 | + V struct{ A, B int } ",inline" |
2519 | +} |
2520 | +type inlineCantPtr struct { |
2521 | + V *struct{ A, B int } ",inline" |
2522 | +} |
2523 | +type inlineDupName struct { |
2524 | + A int |
2525 | + V struct{ A, B int } ",inline" |
2526 | +} |
2527 | + |
2528 | +type MyBytes []byte |
2529 | +type MyBool bool |
2530 | +type MyD []bson.DocElem |
2531 | +type MyM map[string]interface{} |
2532 | + |
2533 | +var truevar = true |
2534 | +var falsevar = false |
2535 | + |
2536 | +var int64var = int64(42) |
2537 | +var int64ptr = &int64var |
2538 | +var intvar = int(42) |
2539 | +var intptr = &intvar |
2540 | + |
2541 | +func parseURL(s string) *url.URL { |
2542 | + u, err := url.Parse(s) |
2543 | + if err != nil { |
2544 | + panic(err) |
2545 | + } |
2546 | + return u |
2547 | +} |
2548 | + |
2549 | +// That's a pretty fun test. It will dump the first item, generate a zero |
2550 | +// value equivalent to the second one, load the dumped data onto it, and then |
2551 | +// verify that the resulting value is deep-equal to the untouched second value. |
2552 | +// Then, it will do the same in the *opposite* direction! |
2553 | +var twoWayCrossItems = []crossTypeItem{ |
2554 | + // int<=>int |
2555 | + {&struct{ I int }{42}, &struct{ I int8 }{42}}, |
2556 | + {&struct{ I int }{42}, &struct{ I int32 }{42}}, |
2557 | + {&struct{ I int }{42}, &struct{ I int64 }{42}}, |
2558 | + {&struct{ I int8 }{42}, &struct{ I int32 }{42}}, |
2559 | + {&struct{ I int8 }{42}, &struct{ I int64 }{42}}, |
2560 | + {&struct{ I int32 }{42}, &struct{ I int64 }{42}}, |
2561 | + |
2562 | + // uint<=>uint |
2563 | + {&struct{ I uint }{42}, &struct{ I uint8 }{42}}, |
2564 | + {&struct{ I uint }{42}, &struct{ I uint32 }{42}}, |
2565 | + {&struct{ I uint }{42}, &struct{ I uint64 }{42}}, |
2566 | + {&struct{ I uint8 }{42}, &struct{ I uint32 }{42}}, |
2567 | + {&struct{ I uint8 }{42}, &struct{ I uint64 }{42}}, |
2568 | + {&struct{ I uint32 }{42}, &struct{ I uint64 }{42}}, |
2569 | + |
2570 | + // float32<=>float64 |
2571 | + {&struct{ I float32 }{42}, &struct{ I float64 }{42}}, |
2572 | + |
2573 | + // int<=>uint |
2574 | + {&struct{ I uint }{42}, &struct{ I int }{42}}, |
2575 | + {&struct{ I uint }{42}, &struct{ I int8 }{42}}, |
2576 | + {&struct{ I uint }{42}, &struct{ I int32 }{42}}, |
2577 | + {&struct{ I uint }{42}, &struct{ I int64 }{42}}, |
2578 | + {&struct{ I uint8 }{42}, &struct{ I int }{42}}, |
2579 | + {&struct{ I uint8 }{42}, &struct{ I int8 }{42}}, |
2580 | + {&struct{ I uint8 }{42}, &struct{ I int32 }{42}}, |
2581 | + {&struct{ I uint8 }{42}, &struct{ I int64 }{42}}, |
2582 | + {&struct{ I uint32 }{42}, &struct{ I int }{42}}, |
2583 | + {&struct{ I uint32 }{42}, &struct{ I int8 }{42}}, |
2584 | + {&struct{ I uint32 }{42}, &struct{ I int32 }{42}}, |
2585 | + {&struct{ I uint32 }{42}, &struct{ I int64 }{42}}, |
2586 | + {&struct{ I uint64 }{42}, &struct{ I int }{42}}, |
2587 | + {&struct{ I uint64 }{42}, &struct{ I int8 }{42}}, |
2588 | + {&struct{ I uint64 }{42}, &struct{ I int32 }{42}}, |
2589 | + {&struct{ I uint64 }{42}, &struct{ I int64 }{42}}, |
2590 | + |
2591 | + // int <=> float |
2592 | + {&struct{ I int }{42}, &struct{ I float64 }{42}}, |
2593 | + |
2594 | + // int <=> bool |
2595 | + {&struct{ I int }{1}, &struct{ I bool }{true}}, |
2596 | + {&struct{ I int }{0}, &struct{ I bool }{false}}, |
2597 | + |
2598 | + // uint <=> float64 |
2599 | + {&struct{ I uint }{42}, &struct{ I float64 }{42}}, |
2600 | + |
2601 | + // uint <=> bool |
2602 | + {&struct{ I uint }{1}, &struct{ I bool }{true}}, |
2603 | + {&struct{ I uint }{0}, &struct{ I bool }{false}}, |
2604 | + |
2605 | + // float64 <=> bool |
2606 | + {&struct{ I float64 }{1}, &struct{ I bool }{true}}, |
2607 | + {&struct{ I float64 }{0}, &struct{ I bool }{false}}, |
2608 | + |
2609 | + // string <=> string and string <=> []byte |
2610 | + {&struct{ S []byte }{[]byte("abc")}, &struct{ S string }{"abc"}}, |
2611 | + {&struct{ S []byte }{[]byte("def")}, &struct{ S bson.Symbol }{"def"}}, |
2612 | + {&struct{ S string }{"ghi"}, &struct{ S bson.Symbol }{"ghi"}}, |
2613 | + |
2614 | + // map <=> struct |
2615 | + {&struct { |
2616 | + A struct { |
2617 | + B, C int |
2618 | + } |
2619 | + }{struct{ B, C int }{1, 2}}, |
2620 | + map[string]map[string]int{"a": map[string]int{"b": 1, "c": 2}}}, |
2621 | + |
2622 | + {&struct{ A bson.Symbol }{"abc"}, map[string]string{"a": "abc"}}, |
2623 | + {&struct{ A bson.Symbol }{"abc"}, map[string][]byte{"a": []byte("abc")}}, |
2624 | + {&struct{ A []byte }{[]byte("abc")}, map[string]string{"a": "abc"}}, |
2625 | + {&struct{ A uint }{42}, map[string]int{"a": 42}}, |
2626 | + {&struct{ A uint }{42}, map[string]float64{"a": 42}}, |
2627 | + {&struct{ A uint }{1}, map[string]bool{"a": true}}, |
2628 | + {&struct{ A int }{42}, map[string]uint{"a": 42}}, |
2629 | + {&struct{ A int }{42}, map[string]float64{"a": 42}}, |
2630 | + {&struct{ A int }{1}, map[string]bool{"a": true}}, |
2631 | + {&struct{ A float64 }{42}, map[string]float32{"a": 42}}, |
2632 | + {&struct{ A float64 }{42}, map[string]int{"a": 42}}, |
2633 | + {&struct{ A float64 }{42}, map[string]uint{"a": 42}}, |
2634 | + {&struct{ A float64 }{1}, map[string]bool{"a": true}}, |
2635 | + {&struct{ A bool }{true}, map[string]int{"a": 1}}, |
2636 | + {&struct{ A bool }{true}, map[string]uint{"a": 1}}, |
2637 | + {&struct{ A bool }{true}, map[string]float64{"a": 1}}, |
2638 | + {&struct{ A **byte }{&byteptr}, map[string]byte{"a": 8}}, |
2639 | + |
2640 | + // url.URL <=> string |
2641 | + {&struct{ URL *url.URL }{parseURL("h://e.c/p")}, map[string]string{"url": "h://e.c/p"}}, |
2642 | + {&struct{ URL url.URL }{*parseURL("h://e.c/p")}, map[string]string{"url": "h://e.c/p"}}, |
2643 | + |
2644 | + // Slices |
2645 | + {&struct{ S []int }{[]int{1, 2, 3}}, map[string][]int{"s": []int{1, 2, 3}}}, |
2646 | + {&struct{ S *[]int }{&[]int{1, 2, 3}}, map[string][]int{"s": []int{1, 2, 3}}}, |
2647 | + |
2648 | + // Conditionals |
2649 | + {&condBool{true}, map[string]bool{"v": true}}, |
2650 | + {&condBool{}, map[string]bool{}}, |
2651 | + {&condInt{1}, map[string]int{"v": 1}}, |
2652 | + {&condInt{}, map[string]int{}}, |
2653 | + {&condUInt{1}, map[string]uint{"v": 1}}, |
2654 | + {&condUInt{}, map[string]uint{}}, |
2655 | + {&condFloat{}, map[string]int{}}, |
2656 | + {&condStr{"yo"}, map[string]string{"v": "yo"}}, |
2657 | + {&condStr{}, map[string]string{}}, |
2658 | + {&condStrNS{"yo"}, map[string]string{"v": "yo"}}, |
2659 | + {&condStrNS{}, map[string]string{}}, |
2660 | + {&condSlice{[]string{"yo"}}, map[string][]string{"v": []string{"yo"}}}, |
2661 | + {&condSlice{}, map[string][]string{}}, |
2662 | + {&condMap{map[string]int{"k": 1}}, bson.M{"v": bson.M{"k": 1}}}, |
2663 | + {&condMap{}, map[string][]string{}}, |
2664 | + {&condIface{"yo"}, map[string]string{"v": "yo"}}, |
2665 | + {&condIface{""}, map[string]string{"v": ""}}, |
2666 | + {&condIface{}, map[string]string{}}, |
2667 | + {&condPtr{&truevar}, map[string]bool{"v": true}}, |
2668 | + {&condPtr{&falsevar}, map[string]bool{"v": false}}, |
2669 | + {&condPtr{}, map[string]string{}}, |
2670 | + |
2671 | + {&condTime{time.Unix(123456789, 123e6)}, map[string]time.Time{"v": time.Unix(123456789, 123e6)}}, |
2672 | + {&condTime{}, map[string]string{}}, |
2673 | + |
2674 | + {&namedCondStr{"yo"}, map[string]string{"myv": "yo"}}, |
2675 | + {&namedCondStr{}, map[string]string{}}, |
2676 | + |
2677 | + {&shortInt{1}, map[string]interface{}{"v": 1}}, |
2678 | + {&shortInt{1 << 30}, map[string]interface{}{"v": 1 << 30}}, |
2679 | + {&shortInt{1 << 31}, map[string]interface{}{"v": int64(1 << 31)}}, |
2680 | + {&shortUint{1 << 30}, map[string]interface{}{"v": 1 << 30}}, |
2681 | + {&shortUint{1 << 31}, map[string]interface{}{"v": int64(1 << 31)}}, |
2682 | + {&shortIface{int64(1) << 31}, map[string]interface{}{"v": int64(1 << 31)}}, |
2683 | + {&shortPtr{int64ptr}, map[string]interface{}{"v": intvar}}, |
2684 | + |
2685 | + {&shortNonEmptyInt{1}, map[string]interface{}{"v": 1}}, |
2686 | + {&shortNonEmptyInt{1 << 31}, map[string]interface{}{"v": int64(1 << 31)}}, |
2687 | + {&shortNonEmptyInt{}, map[string]interface{}{}}, |
2688 | + |
2689 | + {&inlineInt{struct{ A, B int }{1, 2}}, map[string]interface{}{"a": 1, "b": 2}}, |
2690 | + |
2691 | + // []byte <=> MyBytes |
2692 | + {&struct{ B MyBytes }{[]byte("abc")}, map[string]string{"b": "abc"}}, |
2693 | + {&struct{ B MyBytes }{[]byte{}}, map[string]string{"b": ""}}, |
2694 | + {&struct{ B MyBytes }{}, map[string]bool{}}, |
2695 | + {&struct{ B []byte }{[]byte("abc")}, map[string]MyBytes{"b": []byte("abc")}}, |
2696 | + |
2697 | + // bool <=> MyBool |
2698 | + {&struct{ B MyBool }{true}, map[string]bool{"b": true}}, |
2699 | + {&struct{ B MyBool }{}, map[string]bool{"b": false}}, |
2700 | + {&struct{ B MyBool }{}, map[string]string{}}, |
2701 | + {&struct{ B bool }{}, map[string]MyBool{"b": false}}, |
2702 | + |
2703 | + // arrays |
2704 | + {&struct{ V [2]int }{[...]int{1, 2}}, map[string][2]int{"v": [2]int{1, 2}}}, |
2705 | + |
2706 | + // zero time |
2707 | + {&struct{ V time.Time }{}, map[string]interface{}{"v": time.Time{}}}, |
2708 | + |
2709 | + // zero time + 1 second + 1 millisecond; overflows int64 as nanoseconds |
2710 | + {&struct{ V time.Time }{time.Unix(-62135596799, 1e6).Local()}, |
2711 | + map[string]interface{}{"v": time.Unix(-62135596799, 1e6).Local()}}, |
2712 | + |
2713 | + // bson.D <=> []DocElem |
2714 | + {&bson.D{{"a", bson.D{{"b", 1}, {"c", 2}}}}, &bson.D{{"a", bson.D{{"b", 1}, {"c", 2}}}}}, |
2715 | + {&bson.D{{"a", bson.D{{"b", 1}, {"c", 2}}}}, &MyD{{"a", MyD{{"b", 1}, {"c", 2}}}}}, |
2716 | + |
2717 | + // bson.M <=> map |
2718 | + {bson.M{"a": bson.M{"b": 1, "c": 2}}, MyM{"a": MyM{"b": 1, "c": 2}}}, |
2719 | + {bson.M{"a": bson.M{"b": 1, "c": 2}}, map[string]interface{}{"a": map[string]interface{}{"b": 1, "c": 2}}}, |
2720 | +} |
2721 | + |
2722 | +// Same thing, but only one way (obj1 => obj2). |
2723 | +var oneWayCrossItems = []crossTypeItem{ |
2724 | + // map <=> struct |
2725 | + {map[string]interface{}{"a": 1, "b": "2", "c": 3}, |
2726 | + map[string]int{"a": 1, "c": 3}}, |
2727 | + |
2728 | + // Can't decode int into struct. |
2729 | + {bson.M{"a": bson.M{"b": 2}}, &struct{ A bool }{}}, |
2730 | + |
2731 | + // Would get decoded into a int32 too in the opposite direction. |
2732 | + {&shortIface{int64(1) << 30}, map[string]interface{}{"v": 1 << 30}}, |
2733 | +} |
2734 | + |
2735 | +func testCrossPair(c *C, dump interface{}, load interface{}) { |
2736 | + c.Logf("Dump: %#v", dump) |
2737 | + c.Logf("Load: %#v", load) |
2738 | + zero := makeZeroDoc(load) |
2739 | + data, err := bson.Marshal(dump) |
2740 | + c.Assert(err, IsNil) |
2741 | + c.Logf("Dumped: %#v", string(data)) |
2742 | + err = bson.Unmarshal(data, zero) |
2743 | + c.Assert(err, IsNil) |
2744 | + c.Logf("Loaded: %#v", zero) |
2745 | + c.Assert(zero, DeepEquals, load) |
2746 | +} |
2747 | + |
2748 | +func (s *S) TestTwoWayCrossPairs(c *C) { |
2749 | + for _, item := range twoWayCrossItems { |
2750 | + testCrossPair(c, item.obj1, item.obj2) |
2751 | + testCrossPair(c, item.obj2, item.obj1) |
2752 | + } |
2753 | +} |
2754 | + |
2755 | +func (s *S) TestOneWayCrossPairs(c *C) { |
2756 | + for _, item := range oneWayCrossItems { |
2757 | + testCrossPair(c, item.obj1, item.obj2) |
2758 | + } |
2759 | +} |
2760 | + |
2761 | +// -------------------------------------------------------------------------- |
2762 | +// ObjectId hex representation test. |
2763 | + |
2764 | +func (s *S) TestObjectIdHex(c *C) { |
2765 | + id := bson.ObjectIdHex("4d88e15b60f486e428412dc9") |
2766 | + c.Assert(id.String(), Equals, `ObjectIdHex("4d88e15b60f486e428412dc9")`) |
2767 | + c.Assert(id.Hex(), Equals, "4d88e15b60f486e428412dc9") |
2768 | +} |
2769 | + |
2770 | +func (s *S) TestIsObjectIdHex(c *C) { |
2771 | + test := []struct{ id string; valid bool }{ |
2772 | + {"4d88e15b60f486e428412dc9", true}, |
2773 | + {"4d88e15b60f486e428412dc", false}, |
2774 | + {"4d88e15b60f486e428412dc9e", false}, |
2775 | + {"4d88e15b60f486e428412dcx", false}, |
2776 | + } |
2777 | + for _, t := range test { |
2778 | + c.Assert(bson.IsObjectIdHex(t.id), Equals, t.valid) |
2779 | + } |
2780 | +} |
2781 | + |
2782 | +// -------------------------------------------------------------------------- |
2783 | +// ObjectId parts extraction tests. |
2784 | + |
2785 | +type objectIdParts struct { |
2786 | + id bson.ObjectId |
2787 | + timestamp int64 |
2788 | + machine []byte |
2789 | + pid uint16 |
2790 | + counter int32 |
2791 | +} |
2792 | + |
2793 | +var objectIds = []objectIdParts{ |
2794 | + objectIdParts{ |
2795 | + bson.ObjectIdHex("4d88e15b60f486e428412dc9"), |
2796 | + 1300816219, |
2797 | + []byte{0x60, 0xf4, 0x86}, |
2798 | + 0xe428, |
2799 | + 4271561, |
2800 | + }, |
2801 | + objectIdParts{ |
2802 | + bson.ObjectIdHex("000000000000000000000000"), |
2803 | + 0, |
2804 | + []byte{0x00, 0x00, 0x00}, |
2805 | + 0x0000, |
2806 | + 0, |
2807 | + }, |
2808 | + objectIdParts{ |
2809 | + bson.ObjectIdHex("00000000aabbccddee000001"), |
2810 | + 0, |
2811 | + []byte{0xaa, 0xbb, 0xcc}, |
2812 | + 0xddee, |
2813 | + 1, |
2814 | + }, |
2815 | +} |
2816 | + |
2817 | +func (s *S) TestObjectIdPartsExtraction(c *C) { |
2818 | + for i, v := range objectIds { |
2819 | + t := time.Unix(v.timestamp, 0) |
2820 | + c.Assert(v.id.Time(), Equals, t, Commentf("#%d Wrong timestamp value", i)) |
2821 | + c.Assert(v.id.Machine(), DeepEquals, v.machine, Commentf("#%d Wrong machine id value", i)) |
2822 | + c.Assert(v.id.Pid(), Equals, v.pid, Commentf("#%d Wrong pid value", i)) |
2823 | + c.Assert(v.id.Counter(), Equals, v.counter, Commentf("#%d Wrong counter value", i)) |
2824 | + } |
2825 | +} |
2826 | + |
2827 | +func (s *S) TestNow(c *C) { |
2828 | + before := time.Now() |
2829 | + time.Sleep(1e6) |
2830 | + now := bson.Now() |
2831 | + time.Sleep(1e6) |
2832 | + after := time.Now() |
2833 | + c.Assert(now.After(before) && now.Before(after), Equals, true, Commentf("now=%s, before=%s, after=%s", now, before, after)) |
2834 | +} |
2835 | + |
2836 | +// -------------------------------------------------------------------------- |
2837 | +// ObjectId generation tests. |
2838 | + |
2839 | +func (s *S) TestNewObjectId(c *C) { |
2840 | + // Generate 10 ids |
2841 | + ids := make([]bson.ObjectId, 10) |
2842 | + for i := 0; i < 10; i++ { |
2843 | + ids[i] = bson.NewObjectId() |
2844 | + } |
2845 | + for i := 1; i < 10; i++ { |
2846 | + prevId := ids[i-1] |
2847 | + id := ids[i] |
2848 | + // Test for uniqueness among all other 9 generated ids |
2849 | + for j, tid := range ids { |
2850 | + if j != i { |
2851 | + c.Assert(id, Not(Equals), tid, Commentf("Generated ObjectId is not unique")) |
2852 | + } |
2853 | + } |
2854 | + // Check that timestamp was incremented and is within 30 seconds of the previous one |
2855 | + secs := id.Time().Sub(prevId.Time()).Seconds() |
2856 | + c.Assert((secs >= 0 && secs <= 30), Equals, true, Commentf("Wrong timestamp in generated ObjectId")) |
2857 | + // Check that machine ids are the same |
2858 | + c.Assert(id.Machine(), DeepEquals, prevId.Machine()) |
2859 | + // Check that pids are the same |
2860 | + c.Assert(id.Pid(), Equals, prevId.Pid()) |
2861 | + // Test for proper increment |
2862 | + delta := int(id.Counter() - prevId.Counter()) |
2863 | + c.Assert(delta, Equals, 1, Commentf("Wrong increment in generated ObjectId")) |
2864 | + } |
2865 | +} |
2866 | + |
2867 | +func (s *S) TestNewObjectIdWithTime(c *C) { |
2868 | + t := time.Unix(12345678, 0) |
2869 | + id := bson.NewObjectIdWithTime(t) |
2870 | + c.Assert(id.Time(), Equals, t) |
2871 | + c.Assert(id.Machine(), DeepEquals, []byte{0x00, 0x00, 0x00}) |
2872 | + c.Assert(int(id.Pid()), Equals, 0) |
2873 | + c.Assert(int(id.Counter()), Equals, 0) |
2874 | +} |
2875 | + |
2876 | +// -------------------------------------------------------------------------- |
2877 | +// ObjectId JSON marshalling. |
2878 | + |
2879 | +type jsonType struct { |
2880 | + Id *bson.ObjectId |
2881 | +} |
2882 | + |
2883 | +func (s *S) TestObjectIdJSONMarshaling(c *C) { |
2884 | + id := bson.ObjectIdHex("4d88e15b60f486e428412dc9") |
2885 | + v := jsonType{Id: &id} |
2886 | + data, err := json.Marshal(&v) |
2887 | + c.Assert(err, IsNil) |
2888 | + c.Assert(string(data), Equals, `{"Id":"4d88e15b60f486e428412dc9"}`) |
2889 | +} |
2890 | + |
2891 | +func (s *S) TestObjectIdJSONUnmarshaling(c *C) { |
2892 | + data := []byte(`{"Id":"4d88e15b60f486e428412dc9"}`) |
2893 | + v := jsonType{} |
2894 | + err := json.Unmarshal(data, &v) |
2895 | + c.Assert(err, IsNil) |
2896 | + c.Assert(*v.Id, Equals, bson.ObjectIdHex("4d88e15b60f486e428412dc9")) |
2897 | +} |
2898 | + |
2899 | +func (s *S) TestObjectIdJSONUnmarshalingError(c *C) { |
2900 | + v := jsonType{} |
2901 | + err := json.Unmarshal([]byte(`{"Id":"4d88e15b60f486e428412dc9A"}`), &v) |
2902 | + c.Assert(err, ErrorMatches, `Invalid ObjectId in JSON: "4d88e15b60f486e428412dc9A"`) |
2903 | + err = json.Unmarshal([]byte(`{"Id":"4d88e15b60f486e428412dcZ"}`), &v) |
2904 | + c.Assert(err, ErrorMatches, `Invalid ObjectId in JSON: "4d88e15b60f486e428412dcZ" .*`) |
2905 | +} |
2906 | + |
2907 | +// -------------------------------------------------------------------------- |
2908 | +// Some simple benchmarks. |
2909 | + |
2910 | +type BenchT struct { |
2911 | + A, B, C, D, E, F string |
2912 | +} |
2913 | + |
2914 | +func BenchmarkUnmarhsalStruct(b *testing.B) { |
2915 | + v := BenchT{A: "A", D: "D", E: "E"} |
2916 | + data, err := bson.Marshal(&v) |
2917 | + if err != nil { |
2918 | + panic(err) |
2919 | + } |
2920 | + b.ResetTimer() |
2921 | + for i := 0; i < b.N; i++ { |
2922 | + err = bson.Unmarshal(data, &v) |
2923 | + } |
2924 | + if err != nil { |
2925 | + panic(err) |
2926 | + } |
2927 | +} |
2928 | + |
2929 | +func BenchmarkUnmarhsalMap(b *testing.B) { |
2930 | + m := bson.M{"a": "a", "d": "d", "e": "e"} |
2931 | + data, err := bson.Marshal(&m) |
2932 | + if err != nil { |
2933 | + panic(err) |
2934 | + } |
2935 | + b.ResetTimer() |
2936 | + for i := 0; i < b.N; i++ { |
2937 | + err = bson.Unmarshal(data, &m) |
2938 | + } |
2939 | + if err != nil { |
2940 | + panic(err) |
2941 | + } |
2942 | +} |
2943 | |
2944 | === added file 'bson/decode.go' |
2945 | --- bson/decode.go 1970-01-01 00:00:00 +0000 |
2946 | +++ bson/decode.go 2013-04-03 09:16:27 +0000 |
2947 | @@ -0,0 +1,734 @@ |
2948 | +// BSON library for Go |
2949 | +// |
2950 | +// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
2951 | +// |
2952 | +// All rights reserved. |
2953 | +// |
2954 | +// Redistribution and use in source and binary forms, with or without |
2955 | +// modification, are permitted provided that the following conditions are met: |
2956 | +// |
2957 | +// 1. Redistributions of source code must retain the above copyright notice, this |
2958 | +// list of conditions and the following disclaimer. |
2959 | +// 2. Redistributions in binary form must reproduce the above copyright notice, |
2960 | +// this list of conditions and the following disclaimer in the documentation |
2961 | +// and/or other materials provided with the distribution. |
2962 | +// |
2963 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
2964 | +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
2965 | +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
2966 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
2967 | +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
2968 | +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
2969 | +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
2970 | +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
2971 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
2972 | +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
2973 | +// gobson - BSON library for Go. |
2974 | + |
2975 | +package bson |
2976 | + |
2977 | +import ( |
2978 | + "fmt" |
2979 | + "math" |
2980 | + "net/url" |
2981 | + "reflect" |
2982 | + "sync" |
2983 | + "time" |
2984 | +) |
2985 | + |
2986 | +type decoder struct { |
2987 | + in []byte |
2988 | + i int |
2989 | + docType reflect.Type |
2990 | +} |
2991 | + |
2992 | +var typeM = reflect.TypeOf(M{}) |
2993 | + |
2994 | +func newDecoder(in []byte) *decoder { |
2995 | + return &decoder{in, 0, typeM} |
2996 | +} |
2997 | + |
2998 | +// -------------------------------------------------------------------------- |
2999 | +// Some helper functions. |
3000 | + |
3001 | +func corrupted() { |
3002 | + panic("Document is corrupted") |
3003 | +} |
3004 | + |
3005 | +func settableValueOf(i interface{}) reflect.Value { |
3006 | + v := reflect.ValueOf(i) |
3007 | + sv := reflect.New(v.Type()).Elem() |
3008 | + sv.Set(v) |
3009 | + return sv |
3010 | +} |
3011 | + |
3012 | +// -------------------------------------------------------------------------- |
3013 | +// Unmarshaling of documents. |
3014 | + |
3015 | +const ( |
3016 | + setterUnknown = iota |
3017 | + setterNone |
3018 | + setterType |
3019 | + setterAddr |
3020 | +) |
3021 | + |
3022 | +var setterStyle map[reflect.Type]int |
3023 | +var setterIface reflect.Type |
3024 | +var setterMutex sync.RWMutex |
3025 | + |
3026 | +func init() { |
3027 | + var iface Setter |
3028 | + setterIface = reflect.TypeOf(&iface).Elem() |
3029 | + setterStyle = make(map[reflect.Type]int) |
3030 | +} |
3031 | + |
3032 | +func getSetter(outt reflect.Type, out reflect.Value) Setter { |
3033 | + setterMutex.RLock() |
3034 | + style := setterStyle[outt] |
3035 | + setterMutex.RUnlock() |
3036 | + if style == setterNone { |
3037 | + return nil |
3038 | + } |
3039 | + if style == setterUnknown { |
3040 | + setterMutex.Lock() |
3041 | + defer setterMutex.Unlock() |
3042 | + if outt.Implements(setterIface) { |
3043 | + setterStyle[outt] = setterType |
3044 | + } else if reflect.PtrTo(outt).Implements(setterIface) { |
3045 | + setterStyle[outt] = setterAddr |
3046 | + } else { |
3047 | + setterStyle[outt] = setterNone |
3048 | + return nil |
3049 | + } |
3050 | + style = setterStyle[outt] |
3051 | + } |
3052 | + if style == setterAddr { |
3053 | + if !out.CanAddr() { |
3054 | + return nil |
3055 | + } |
3056 | + out = out.Addr() |
3057 | + } else if outt.Kind() == reflect.Ptr && out.IsNil() { |
3058 | + out.Set(reflect.New(outt.Elem())) |
3059 | + } |
3060 | + return out.Interface().(Setter) |
3061 | +} |
3062 | + |
3063 | +func (d *decoder) readDocTo(out reflect.Value) { |
3064 | + var elemType reflect.Type |
3065 | + outt := out.Type() |
3066 | + outk := outt.Kind() |
3067 | + |
3068 | + for { |
3069 | + if outk == reflect.Ptr && out.IsNil() { |
3070 | + out.Set(reflect.New(outt.Elem())) |
3071 | + } |
3072 | + if setter := getSetter(outt, out); setter != nil { |
3073 | + var raw Raw |
3074 | + d.readDocTo(reflect.ValueOf(&raw)) |
3075 | + err := setter.SetBSON(raw) |
3076 | + if _, ok := err.(*TypeError); err != nil && !ok { |
3077 | + panic(err) |
3078 | + } |
3079 | + return |
3080 | + } |
3081 | + if outk == reflect.Ptr { |
3082 | + out = out.Elem() |
3083 | + outt = out.Type() |
3084 | + outk = out.Kind() |
3085 | + continue |
3086 | + } |
3087 | + break |
3088 | + } |
3089 | + |
3090 | + var fieldsMap map[string]fieldInfo |
3091 | + start := d.i |
3092 | + |
3093 | + origout := out |
3094 | + if outk == reflect.Interface { |
3095 | + if d.docType.Kind() == reflect.Map { |
3096 | + mv := reflect.MakeMap(d.docType) |
3097 | + out.Set(mv) |
3098 | + out = mv |
3099 | + } else { |
3100 | + dv := reflect.New(d.docType).Elem() |
3101 | + out.Set(dv) |
3102 | + out = dv |
3103 | + } |
3104 | + outt = out.Type() |
3105 | + outk = outt.Kind() |
3106 | + } |
3107 | + |
3108 | + docType := d.docType |
3109 | + switch outk { |
3110 | + case reflect.Map: |
3111 | + if outt.Key().Kind() != reflect.String { |
3112 | + panic("BSON map must have string keys. Got: " + outt.String()) |
3113 | + } |
3114 | + elemType = outt.Elem() |
3115 | + if elemType == typeIface { |
3116 | + d.docType = outt |
3117 | + } |
3118 | + if out.IsNil() { |
3119 | + out.Set(reflect.MakeMap(out.Type())) |
3120 | + } else if out.Len() > 0 { |
3121 | + var none reflect.Value |
3122 | + for _, k := range out.MapKeys() { |
3123 | + out.SetMapIndex(k, none) |
3124 | + } |
3125 | + } |
3126 | + case reflect.Struct: |
3127 | + if outt != typeRaw { |
3128 | + sinfo, err := getStructInfo(out.Type()) |
3129 | + if err != nil { |
3130 | + panic(err) |
3131 | + } |
3132 | + fieldsMap = sinfo.FieldsMap |
3133 | + out.Set(sinfo.Zero) |
3134 | + } |
3135 | + case reflect.Slice: |
3136 | + if outt.Elem() == typeDocElem { |
3137 | + origout.Set(d.readDocElems(outt)) |
3138 | + return |
3139 | + } |
3140 | + fallthrough |
3141 | + default: |
3142 | + panic("Unsupported document type for unmarshalling: " + out.Type().String()) |
3143 | + } |
3144 | + |
3145 | + end := d.i - 4 + int(d.readInt32()) |
3146 | + if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { |
3147 | + corrupted() |
3148 | + } |
3149 | + for d.in[d.i] != '\x00' { |
3150 | + kind := d.readByte() |
3151 | + name := d.readCStr() |
3152 | + if d.i >= end { |
3153 | + corrupted() |
3154 | + } |
3155 | + |
3156 | + switch outk { |
3157 | + case reflect.Map: |
3158 | + e := reflect.New(elemType).Elem() |
3159 | + if d.readElemTo(e, kind) { |
3160 | + out.SetMapIndex(reflect.ValueOf(name), e) |
3161 | + } |
3162 | + case reflect.Struct: |
3163 | + if outt == typeRaw { |
3164 | + d.readElemTo(blackHole, kind) |
3165 | + } else { |
3166 | + if info, ok := fieldsMap[name]; ok { |
3167 | + if info.Inline == nil { |
3168 | + d.readElemTo(out.Field(info.Num), kind) |
3169 | + } else { |
3170 | + d.readElemTo(out.FieldByIndex(info.Inline), kind) |
3171 | + } |
3172 | + } else { |
3173 | + d.dropElem(kind) |
3174 | + } |
3175 | + } |
3176 | + case reflect.Slice: |
3177 | + } |
3178 | + |
3179 | + if d.i >= end { |
3180 | + corrupted() |
3181 | + } |
3182 | + } |
3183 | + d.i++ // '\x00' |
3184 | + if d.i != end { |
3185 | + corrupted() |
3186 | + } |
3187 | + d.docType = docType |
3188 | + |
3189 | + if outt == typeRaw { |
3190 | + out.Set(reflect.ValueOf(Raw{0x03, d.in[start:d.i]})) |
3191 | + } |
3192 | +} |
3193 | + |
3194 | +func (d *decoder) readArrayDocTo(out reflect.Value) { |
3195 | + end := d.i - 4 + int(d.readInt32()) |
3196 | + if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { |
3197 | + corrupted() |
3198 | + } |
3199 | + i := 0 |
3200 | + l := out.Len() |
3201 | + for d.in[d.i] != '\x00' { |
3202 | + if i >= l { |
3203 | + panic("Length mismatch on array field") |
3204 | + } |
3205 | + kind := d.readByte() |
3206 | + for d.i < end && d.in[d.i] != '\x00' { |
3207 | + d.i++ |
3208 | + } |
3209 | + if d.i >= end { |
3210 | + corrupted() |
3211 | + } |
3212 | + d.i++ |
3213 | + d.readElemTo(out.Index(i), kind) |
3214 | + if d.i >= end { |
3215 | + corrupted() |
3216 | + } |
3217 | + i++ |
3218 | + } |
3219 | + if i != l { |
3220 | + panic("Length mismatch on array field") |
3221 | + } |
3222 | + d.i++ // '\x00' |
3223 | + if d.i != end { |
3224 | + corrupted() |
3225 | + } |
3226 | +} |
3227 | + |
3228 | +func (d *decoder) readSliceDoc(t reflect.Type) interface{} { |
3229 | + tmp := make([]reflect.Value, 0, 8) |
3230 | + elemType := t.Elem() |
3231 | + |
3232 | + end := d.i - 4 + int(d.readInt32()) |
3233 | + if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { |
3234 | + corrupted() |
3235 | + } |
3236 | + for d.in[d.i] != '\x00' { |
3237 | + kind := d.readByte() |
3238 | + for d.i < end && d.in[d.i] != '\x00' { |
3239 | + d.i++ |
3240 | + } |
3241 | + if d.i >= end { |
3242 | + corrupted() |
3243 | + } |
3244 | + d.i++ |
3245 | + e := reflect.New(elemType).Elem() |
3246 | + if d.readElemTo(e, kind) { |
3247 | + tmp = append(tmp, e) |
3248 | + } |
3249 | + if d.i >= end { |
3250 | + corrupted() |
3251 | + } |
3252 | + } |
3253 | + d.i++ // '\x00' |
3254 | + if d.i != end { |
3255 | + corrupted() |
3256 | + } |
3257 | + |
3258 | + n := len(tmp) |
3259 | + slice := reflect.MakeSlice(t, n, n) |
3260 | + for i := 0; i != n; i++ { |
3261 | + slice.Index(i).Set(tmp[i]) |
3262 | + } |
3263 | + return slice.Interface() |
3264 | +} |
3265 | + |
3266 | +var typeSlice = reflect.TypeOf([]interface{}{}) |
3267 | +var typeIface = typeSlice.Elem() |
3268 | + |
3269 | +func (d *decoder) readDocElems(typ reflect.Type) reflect.Value { |
3270 | + docType := d.docType |
3271 | + d.docType = typ |
3272 | + slice := make([]DocElem, 0, 8) |
3273 | + d.readDocWith(func(kind byte, name string) { |
3274 | + e := DocElem{Name: name} |
3275 | + v := reflect.ValueOf(&e.Value) |
3276 | + if d.readElemTo(v.Elem(), kind) { |
3277 | + slice = append(slice, e) |
3278 | + } |
3279 | + }) |
3280 | + slicev := reflect.New(typ).Elem() |
3281 | + slicev.Set(reflect.ValueOf(slice)) |
3282 | + d.docType = docType |
3283 | + return slicev |
3284 | +} |
3285 | + |
3286 | +func (d *decoder) readDocWith(f func(kind byte, name string)) { |
3287 | + end := d.i - 4 + int(d.readInt32()) |
3288 | + if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { |
3289 | + corrupted() |
3290 | + } |
3291 | + for d.in[d.i] != '\x00' { |
3292 | + kind := d.readByte() |
3293 | + name := d.readCStr() |
3294 | + if d.i >= end { |
3295 | + corrupted() |
3296 | + } |
3297 | + f(kind, name) |
3298 | + if d.i >= end { |
3299 | + corrupted() |
3300 | + } |
3301 | + } |
3302 | + d.i++ // '\x00' |
3303 | + if d.i != end { |
3304 | + corrupted() |
3305 | + } |
3306 | +} |
3307 | + |
3308 | +// -------------------------------------------------------------------------- |
3309 | +// Unmarshaling of individual elements within a document. |
3310 | + |
3311 | +var blackHole = settableValueOf(struct{}{}) |
3312 | + |
3313 | +func (d *decoder) dropElem(kind byte) { |
3314 | + d.readElemTo(blackHole, kind) |
3315 | +} |
3316 | + |
3317 | +// Attempt to decode an element from the document and put it into out. |
3318 | +// If the types are not compatible, the returned ok value will be |
3319 | +// false and out will be unchanged. |
3320 | +func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { |
3321 | + |
3322 | + start := d.i |
3323 | + |
3324 | + if kind == '\x03' { |
3325 | + // Special case for documents. Delegate to readDocTo(). |
3326 | + switch out.Kind() { |
3327 | + case reflect.Interface, reflect.Ptr, reflect.Struct, reflect.Map: |
3328 | + d.readDocTo(out) |
3329 | + default: |
3330 | + if _, ok := out.Interface().(D); ok { |
3331 | + out.Set(d.readDocElems(out.Type())) |
3332 | + } else { |
3333 | + d.readDocTo(blackHole) |
3334 | + } |
3335 | + } |
3336 | + return true |
3337 | + } |
3338 | + |
3339 | + var in interface{} |
3340 | + |
3341 | + switch kind { |
3342 | + case 0x01: // Float64 |
3343 | + in = d.readFloat64() |
3344 | + case 0x02: // UTF-8 string |
3345 | + in = d.readStr() |
3346 | + case 0x03: // Document |
3347 | + panic("Can't happen. Handled above.") |
3348 | + case 0x04: // Array |
3349 | + outt := out.Type() |
3350 | + for outt.Kind() == reflect.Ptr { |
3351 | + outt = outt.Elem() |
3352 | + } |
3353 | + switch outt.Kind() { |
3354 | + case reflect.Array: |
3355 | + d.readArrayDocTo(out) |
3356 | + return true |
3357 | + case reflect.Slice: |
3358 | + in = d.readSliceDoc(outt) |
3359 | + default: |
3360 | + in = d.readSliceDoc(typeSlice) |
3361 | + } |
3362 | + case 0x05: // Binary |
3363 | + b := d.readBinary() |
3364 | + if b.Kind == 0x00 || b.Kind == 0x02 { |
3365 | + in = b.Data |
3366 | + } else { |
3367 | + in = b |
3368 | + } |
3369 | + case 0x06: // Undefined (obsolete, but still seen in the wild) |
3370 | + in = Undefined |
3371 | + case 0x07: // ObjectId |
3372 | + in = ObjectId(d.readBytes(12)) |
3373 | + case 0x08: // Bool |
3374 | + in = d.readBool() |
3375 | + case 0x09: // Timestamp |
3376 | + // MongoDB handles timestamps as milliseconds. |
3377 | + i := d.readInt64() |
3378 | + if i == -62135596800000 { |
3379 | + in = time.Time{} // In UTC for convenience. |
3380 | + } else { |
3381 | + in = time.Unix(i/1e3, i%1e3*1e6) |
3382 | + } |
3383 | + case 0x0A: // Nil |
3384 | + in = nil |
3385 | + case 0x0B: // RegEx |
3386 | + in = d.readRegEx() |
3387 | + case 0x0D: // JavaScript without scope |
3388 | + in = JavaScript{Code: d.readStr()} |
3389 | + case 0x0E: // Symbol |
3390 | + in = Symbol(d.readStr()) |
3391 | + case 0x0F: // JavaScript with scope |
3392 | + d.i += 4 // Skip length |
3393 | + js := JavaScript{d.readStr(), make(M)} |
3394 | + d.readDocTo(reflect.ValueOf(js.Scope)) |
3395 | + in = js |
3396 | + case 0x10: // Int32 |
3397 | + in = int(d.readInt32()) |
3398 | + case 0x11: // Mongo-specific timestamp |
3399 | + in = MongoTimestamp(d.readInt64()) |
3400 | + case 0x12: // Int64 |
3401 | + in = d.readInt64() |
3402 | + case 0x7F: // Max key |
3403 | + in = MaxKey |
3404 | + case 0xFF: // Min key |
3405 | + in = MinKey |
3406 | + default: |
3407 | + panic(fmt.Sprintf("Unknown element kind (0x%02X)", kind)) |
3408 | + } |
3409 | + |
3410 | + outt := out.Type() |
3411 | + |
3412 | + if outt == typeRaw { |
3413 | + out.Set(reflect.ValueOf(Raw{kind, d.in[start:d.i]})) |
3414 | + return true |
3415 | + } |
3416 | + |
3417 | + if setter := getSetter(outt, out); setter != nil { |
3418 | + err := setter.SetBSON(Raw{kind, d.in[start:d.i]}) |
3419 | + if err == SetZero { |
3420 | + out.Set(reflect.Zero(outt)) |
3421 | + return true |
3422 | + } |
3423 | + if err == nil { |
3424 | + return true |
3425 | + } |
3426 | + if _, ok := err.(*TypeError); !ok { |
3427 | + panic(err) |
3428 | + } |
3429 | + return false |
3430 | + } |
3431 | + |
3432 | + if in == nil { |
3433 | + out.Set(reflect.Zero(outt)) |
3434 | + return true |
3435 | + } |
3436 | + |
3437 | + outk := outt.Kind() |
3438 | + |
3439 | + // Dereference and initialize pointer if necessary. |
3440 | + first := true |
3441 | + for outk == reflect.Ptr { |
3442 | + if !out.IsNil() { |
3443 | + out = out.Elem() |
3444 | + } else { |
3445 | + elem := reflect.New(outt.Elem()) |
3446 | + if first { |
3447 | + // Only set if value is compatible. |
3448 | + first = false |
3449 | + defer func(out, elem reflect.Value) { |
3450 | + if good { |
3451 | + out.Set(elem) |
3452 | + } |
3453 | + }(out, elem) |
3454 | + } else { |
3455 | + out.Set(elem) |
3456 | + } |
3457 | + out = elem |
3458 | + } |
3459 | + outt = out.Type() |
3460 | + outk = outt.Kind() |
3461 | + } |
3462 | + |
3463 | + inv := reflect.ValueOf(in) |
3464 | + if outt == inv.Type() { |
3465 | + out.Set(inv) |
3466 | + return true |
3467 | + } |
3468 | + |
3469 | + switch outk { |
3470 | + case reflect.Interface: |
3471 | + out.Set(inv) |
3472 | + return true |
3473 | + case reflect.String: |
3474 | + switch inv.Kind() { |
3475 | + case reflect.String: |
3476 | + out.SetString(inv.String()) |
3477 | + return true |
3478 | + case reflect.Slice: |
3479 | + if b, ok := in.([]byte); ok { |
3480 | + out.SetString(string(b)) |
3481 | + return true |
3482 | + } |
3483 | + } |
3484 | + case reflect.Slice, reflect.Array: |
3485 | + // Remember, array (0x04) slices are built with the correct |
3486 | + // element type. If we are here, must be a cross BSON kind |
3487 | + // conversion (e.g. 0x05 unmarshalling on string). |
3488 | + if outt.Elem().Kind() != reflect.Uint8 { |
3489 | + break |
3490 | + } |
3491 | + switch inv.Kind() { |
3492 | + case reflect.String: |
3493 | + slice := []byte(inv.String()) |
3494 | + out.Set(reflect.ValueOf(slice)) |
3495 | + return true |
3496 | + case reflect.Slice: |
3497 | + switch outt.Kind() { |
3498 | + case reflect.Array: |
3499 | + reflect.Copy(out, inv) |
3500 | + case reflect.Slice: |
3501 | + out.SetBytes(inv.Bytes()) |
3502 | + } |
3503 | + return true |
3504 | + } |
3505 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
3506 | + switch inv.Kind() { |
3507 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
3508 | + out.SetInt(inv.Int()) |
3509 | + return true |
3510 | + case reflect.Float32, reflect.Float64: |
3511 | + out.SetInt(int64(inv.Float())) |
3512 | + return true |
3513 | + case reflect.Bool: |
3514 | + if inv.Bool() { |
3515 | + out.SetInt(1) |
3516 | + } else { |
3517 | + out.SetInt(0) |
3518 | + } |
3519 | + return true |
3520 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
3521 | + panic("Can't happen. No uint types in BSON?") |
3522 | + } |
3523 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
3524 | + switch inv.Kind() { |
3525 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
3526 | + out.SetUint(uint64(inv.Int())) |
3527 | + return true |
3528 | + case reflect.Float32, reflect.Float64: |
3529 | + out.SetUint(uint64(inv.Float())) |
3530 | + return true |
3531 | + case reflect.Bool: |
3532 | + if inv.Bool() { |
3533 | + out.SetUint(1) |
3534 | + } else { |
3535 | + out.SetUint(0) |
3536 | + } |
3537 | + return true |
3538 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
3539 | + panic("Can't happen. No uint types in BSON.") |
3540 | + } |
3541 | + case reflect.Float32, reflect.Float64: |
3542 | + switch inv.Kind() { |
3543 | + case reflect.Float32, reflect.Float64: |
3544 | + out.SetFloat(inv.Float()) |
3545 | + return true |
3546 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
3547 | + out.SetFloat(float64(inv.Int())) |
3548 | + return true |
3549 | + case reflect.Bool: |
3550 | + if inv.Bool() { |
3551 | + out.SetFloat(1) |
3552 | + } else { |
3553 | + out.SetFloat(0) |
3554 | + } |
3555 | + return true |
3556 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
3557 | + panic("Can't happen. No uint types in BSON?") |
3558 | + } |
3559 | + case reflect.Bool: |
3560 | + switch inv.Kind() { |
3561 | + case reflect.Bool: |
3562 | + out.SetBool(inv.Bool()) |
3563 | + return true |
3564 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
3565 | + out.SetBool(inv.Int() != 0) |
3566 | + return true |
3567 | + case reflect.Float32, reflect.Float64: |
3568 | + out.SetBool(inv.Float() != 0) |
3569 | + return true |
3570 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
3571 | + panic("Can't happen. No uint types in BSON?") |
3572 | + } |
3573 | + case reflect.Struct: |
3574 | + if outt == typeURL && inv.Kind() == reflect.String { |
3575 | + u, err := url.Parse(inv.String()) |
3576 | + if err != nil { |
3577 | + panic(err) |
3578 | + } |
3579 | + out.Set(reflect.ValueOf(u).Elem()) |
3580 | + return true |
3581 | + } |
3582 | + } |
3583 | + |
3584 | + return false |
3585 | +} |
3586 | + |
3587 | +// -------------------------------------------------------------------------- |
3588 | +// Parsers of basic types. |
3589 | + |
3590 | +func (d *decoder) readRegEx() RegEx { |
3591 | + re := RegEx{} |
3592 | + re.Pattern = d.readCStr() |
3593 | + re.Options = d.readCStr() |
3594 | + return re |
3595 | +} |
3596 | + |
3597 | +func (d *decoder) readBinary() Binary { |
3598 | + l := d.readInt32() |
3599 | + b := Binary{} |
3600 | + b.Kind = d.readByte() |
3601 | + b.Data = d.readBytes(l) |
3602 | + if b.Kind == 0x02 { |
3603 | + // Weird obsolete format with redundant length. |
3604 | + b.Data = b.Data[4:] |
3605 | + } |
3606 | + return b |
3607 | +} |
3608 | + |
3609 | +func (d *decoder) readStr() string { |
3610 | + l := d.readInt32() |
3611 | + b := d.readBytes(l - 1) |
3612 | + if d.readByte() != '\x00' { |
3613 | + corrupted() |
3614 | + } |
3615 | + return string(b) |
3616 | +} |
3617 | + |
3618 | +func (d *decoder) readCStr() string { |
3619 | + start := d.i |
3620 | + end := start |
3621 | + l := len(d.in) |
3622 | + for ; end != l; end++ { |
3623 | + if d.in[end] == '\x00' { |
3624 | + break |
3625 | + } |
3626 | + } |
3627 | + d.i = end + 1 |
3628 | + if d.i > l { |
3629 | + corrupted() |
3630 | + } |
3631 | + return string(d.in[start:end]) |
3632 | +} |
3633 | + |
3634 | +func (d *decoder) readBool() bool { |
3635 | + if d.readByte() == 1 { |
3636 | + return true |
3637 | + } |
3638 | + return false |
3639 | +} |
3640 | + |
3641 | +func (d *decoder) readFloat64() float64 { |
3642 | + return math.Float64frombits(uint64(d.readInt64())) |
3643 | +} |
3644 | + |
3645 | +func (d *decoder) readInt32() int32 { |
3646 | + b := d.readBytes(4) |
3647 | + return int32((uint32(b[0]) << 0) | |
3648 | + (uint32(b[1]) << 8) | |
3649 | + (uint32(b[2]) << 16) | |
3650 | + (uint32(b[3]) << 24)) |
3651 | +} |
3652 | + |
3653 | +func (d *decoder) readInt64() int64 { |
3654 | + b := d.readBytes(8) |
3655 | + return int64((uint64(b[0]) << 0) | |
3656 | + (uint64(b[1]) << 8) | |
3657 | + (uint64(b[2]) << 16) | |
3658 | + (uint64(b[3]) << 24) | |
3659 | + (uint64(b[4]) << 32) | |
3660 | + (uint64(b[5]) << 40) | |
3661 | + (uint64(b[6]) << 48) | |
3662 | + (uint64(b[7]) << 56)) |
3663 | +} |
3664 | + |
3665 | +func (d *decoder) readByte() byte { |
3666 | + i := d.i |
3667 | + d.i++ |
3668 | + if d.i > len(d.in) { |
3669 | + corrupted() |
3670 | + } |
3671 | + return d.in[i] |
3672 | +} |
3673 | + |
3674 | +func (d *decoder) readBytes(length int32) []byte { |
3675 | + start := d.i |
3676 | + d.i += int(length) |
3677 | + if d.i > len(d.in) { |
3678 | + corrupted() |
3679 | + } |
3680 | + return d.in[start : start+int(length)] |
3681 | +} |
3682 | |
3683 | === added file 'bson/encode.go' |
3684 | --- bson/encode.go 1970-01-01 00:00:00 +0000 |
3685 | +++ bson/encode.go 2013-04-03 09:16:27 +0000 |
3686 | @@ -0,0 +1,430 @@ |
3687 | +// BSON library for Go |
3688 | +// |
3689 | +// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
3690 | +// |
3691 | +// All rights reserved. |
3692 | +// |
3693 | +// Redistribution and use in source and binary forms, with or without |
3694 | +// modification, are permitted provided that the following conditions are met: |
3695 | +// |
3696 | +// 1. Redistributions of source code must retain the above copyright notice, this |
3697 | +// list of conditions and the following disclaimer. |
3698 | +// 2. Redistributions in binary form must reproduce the above copyright notice, |
3699 | +// this list of conditions and the following disclaimer in the documentation |
3700 | +// and/or other materials provided with the distribution. |
3701 | +// |
3702 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
3703 | +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
3704 | +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
3705 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
3706 | +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
3707 | +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
3708 | +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
3709 | +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
3710 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
3711 | +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
3712 | +// gobson - BSON library for Go. |
3713 | + |
3714 | +package bson |
3715 | + |
3716 | +import ( |
3717 | + "math" |
3718 | + "net/url" |
3719 | + "reflect" |
3720 | + "strconv" |
3721 | + "time" |
3722 | +) |
3723 | + |
3724 | +// -------------------------------------------------------------------------- |
3725 | +// Some internal infrastructure. |
3726 | + |
3727 | +var ( |
3728 | + typeBinary = reflect.TypeOf(Binary{}) |
3729 | + typeObjectId = reflect.TypeOf(ObjectId("")) |
3730 | + typeSymbol = reflect.TypeOf(Symbol("")) |
3731 | + typeMongoTimestamp = reflect.TypeOf(MongoTimestamp(0)) |
3732 | + typeOrderKey = reflect.TypeOf(MinKey) |
3733 | + typeDocElem = reflect.TypeOf(DocElem{}) |
3734 | + typeRaw = reflect.TypeOf(Raw{}) |
3735 | + typeURL = reflect.TypeOf(url.URL{}) |
3736 | + typeTime = reflect.TypeOf(time.Time{}) |
3737 | +) |
3738 | + |
3739 | +const itoaCacheSize = 32 |
3740 | + |
3741 | +var itoaCache []string |
3742 | + |
3743 | +func init() { |
3744 | + itoaCache = make([]string, itoaCacheSize) |
3745 | + for i := 0; i != itoaCacheSize; i++ { |
3746 | + itoaCache[i] = strconv.Itoa(i) |
3747 | + } |
3748 | +} |
3749 | + |
3750 | +func itoa(i int) string { |
3751 | + if i < itoaCacheSize { |
3752 | + return itoaCache[i] |
3753 | + } |
3754 | + return strconv.Itoa(i) |
3755 | +} |
3756 | + |
3757 | +// -------------------------------------------------------------------------- |
3758 | +// Marshaling of the document value itself. |
3759 | + |
3760 | +type encoder struct { |
3761 | + out []byte |
3762 | +} |
3763 | + |
3764 | +func (e *encoder) addDoc(v reflect.Value) { |
3765 | + for { |
3766 | + if vi, ok := v.Interface().(Getter); ok { |
3767 | + getv, err := vi.GetBSON() |
3768 | + if err != nil { |
3769 | + panic(err) |
3770 | + } |
3771 | + v = reflect.ValueOf(getv) |
3772 | + continue |
3773 | + } |
3774 | + if v.Kind() == reflect.Ptr { |
3775 | + v = v.Elem() |
3776 | + continue |
3777 | + } |
3778 | + break |
3779 | + } |
3780 | + |
3781 | + if v.Type() == typeRaw { |
3782 | + raw := v.Interface().(Raw) |
3783 | + if raw.Kind != 0x03 && raw.Kind != 0x00 { |
3784 | + panic("Attempted to unmarshal Raw kind " + strconv.Itoa(int(raw.Kind)) + " as a document") |
3785 | + } |
3786 | + e.addBytes(raw.Data...) |
3787 | + return |
3788 | + } |
3789 | + |
3790 | + start := e.reserveInt32() |
3791 | + |
3792 | + switch v.Kind() { |
3793 | + case reflect.Map: |
3794 | + e.addMap(v) |
3795 | + case reflect.Struct: |
3796 | + e.addStruct(v) |
3797 | + case reflect.Array, reflect.Slice: |
3798 | + e.addSlice(v) |
3799 | + default: |
3800 | + panic("Can't marshal " + v.Type().String() + " as a BSON document") |
3801 | + } |
3802 | + |
3803 | + e.addBytes(0) |
3804 | + e.setInt32(start, int32(len(e.out)-start)) |
3805 | +} |
3806 | + |
3807 | +func (e *encoder) addMap(v reflect.Value) { |
3808 | + for _, k := range v.MapKeys() { |
3809 | + e.addElem(k.String(), v.MapIndex(k), false) |
3810 | + } |
3811 | +} |
3812 | + |
3813 | +func (e *encoder) addStruct(v reflect.Value) { |
3814 | + sinfo, err := getStructInfo(v.Type()) |
3815 | + if err != nil { |
3816 | + panic(err) |
3817 | + } |
3818 | + var value reflect.Value |
3819 | + for _, info := range sinfo.FieldsList { |
3820 | + if info.Inline == nil { |
3821 | + value = v.Field(info.Num) |
3822 | + } else { |
3823 | + value = v.FieldByIndex(info.Inline) |
3824 | + } |
3825 | + if info.OmitEmpty && isZero(value) { |
3826 | + continue |
3827 | + } |
3828 | + e.addElem(info.Key, value, info.MinSize) |
3829 | + } |
3830 | +} |
3831 | + |
3832 | +func isZero(v reflect.Value) bool { |
3833 | + switch v.Kind() { |
3834 | + case reflect.String: |
3835 | + return len(v.String()) == 0 |
3836 | + case reflect.Ptr, reflect.Interface: |
3837 | + return v.IsNil() |
3838 | + case reflect.Slice: |
3839 | + return v.Len() == 0 |
3840 | + case reflect.Map: |
3841 | + return v.Len() == 0 |
3842 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
3843 | + return v.Int() == 0 |
3844 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
3845 | + return v.Uint() == 0 |
3846 | + case reflect.Float32, reflect.Float64: |
3847 | + return v.Float() == 0 |
3848 | + case reflect.Bool: |
3849 | + return !v.Bool() |
3850 | + case reflect.Struct: |
3851 | + if v.Type() == typeTime { |
3852 | + return v.Interface().(time.Time).IsZero() |
3853 | + } |
3854 | + } |
3855 | + return false |
3856 | +} |
3857 | + |
3858 | +func (e *encoder) addSlice(v reflect.Value) { |
3859 | + if d, ok := v.Interface().(D); ok { |
3860 | + for _, elem := range d { |
3861 | + e.addElem(elem.Name, reflect.ValueOf(elem.Value), false) |
3862 | + } |
3863 | + } else if v.Type().Elem() == typeDocElem { |
3864 | + l := v.Len() |
3865 | + for i := 0; i < l; i++ { |
3866 | + elem := v.Index(i).Interface().(DocElem) |
3867 | + e.addElem(elem.Name, reflect.ValueOf(elem.Value), false) |
3868 | + } |
3869 | + } else { |
3870 | + l := v.Len() |
3871 | + for i := 0; i < l; i++ { |
3872 | + e.addElem(itoa(i), v.Index(i), false) |
3873 | + } |
3874 | + } |
3875 | +} |
3876 | + |
3877 | +// -------------------------------------------------------------------------- |
3878 | +// Marshaling of elements in a document. |
3879 | + |
3880 | +func (e *encoder) addElemName(kind byte, name string) { |
3881 | + e.addBytes(kind) |
3882 | + e.addBytes([]byte(name)...) |
3883 | + e.addBytes(0) |
3884 | +} |
3885 | + |
3886 | +func (e *encoder) addElem(name string, v reflect.Value, minSize bool) { |
3887 | + |
3888 | + if !v.IsValid() { |
3889 | + e.addElemName('\x0A', name) |
3890 | + return |
3891 | + } |
3892 | + |
3893 | + if getter, ok := v.Interface().(Getter); ok { |
3894 | + getv, err := getter.GetBSON() |
3895 | + if err != nil { |
3896 | + panic(err) |
3897 | + } |
3898 | + e.addElem(name, reflect.ValueOf(getv), minSize) |
3899 | + return |
3900 | + } |
3901 | + |
3902 | + switch v.Kind() { |
3903 | + |
3904 | + case reflect.Interface: |
3905 | + e.addElem(name, v.Elem(), minSize) |
3906 | + |
3907 | + case reflect.Ptr: |
3908 | + e.addElem(name, v.Elem(), minSize) |
3909 | + |
3910 | + case reflect.String: |
3911 | + s := v.String() |
3912 | + switch v.Type() { |
3913 | + case typeObjectId: |
3914 | + if len(s) != 12 { |
3915 | + panic("ObjectIDs must be exactly 12 bytes long (got " + |
3916 | + strconv.Itoa(len(s)) + ")") |
3917 | + } |
3918 | + e.addElemName('\x07', name) |
3919 | + e.addBytes([]byte(s)...) |
3920 | + case typeSymbol: |
3921 | + e.addElemName('\x0E', name) |
3922 | + e.addStr(s) |
3923 | + default: |
3924 | + e.addElemName('\x02', name) |
3925 | + e.addStr(s) |
3926 | + } |
3927 | + |
3928 | + case reflect.Float32, reflect.Float64: |
3929 | + e.addElemName('\x01', name) |
3930 | + e.addInt64(int64(math.Float64bits(v.Float()))) |
3931 | + |
3932 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
3933 | + u := v.Uint() |
3934 | + if int64(u) < 0 { |
3935 | + panic("BSON has no uint64 type, and value is too large to fit correctly in an int64") |
3936 | + } else if u <= math.MaxInt32 && (minSize || v.Kind() <= reflect.Uint32) { |
3937 | + e.addElemName('\x10', name) |
3938 | + e.addInt32(int32(u)) |
3939 | + } else { |
3940 | + e.addElemName('\x12', name) |
3941 | + e.addInt64(int64(u)) |
3942 | + } |
3943 | + |
3944 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
3945 | + if v.Type().Kind() <= reflect.Int32 { |
3946 | + e.addElemName('\x10', name) |
3947 | + e.addInt32(int32(v.Int())) |
3948 | + } else { |
3949 | + switch v.Type() { |
3950 | + case typeMongoTimestamp: |
3951 | + e.addElemName('\x11', name) |
3952 | + e.addInt64(v.Int()) |
3953 | + |
3954 | + case typeOrderKey: |
3955 | + if v.Int() == int64(MaxKey) { |
3956 | + e.addElemName('\x7F', name) |
3957 | + } else { |
3958 | + e.addElemName('\xFF', name) |
3959 | + } |
3960 | + |
3961 | + default: |
3962 | + i := v.Int() |
3963 | + if minSize && i >= math.MinInt32 && i <= math.MaxInt32 { |
3964 | + // It fits into an int32, encode as such. |
3965 | + e.addElemName('\x10', name) |
3966 | + e.addInt32(int32(i)) |
3967 | + } else { |
3968 | + e.addElemName('\x12', name) |
3969 | + e.addInt64(i) |
3970 | + } |
3971 | + } |
3972 | + } |
3973 | + |
3974 | + case reflect.Bool: |
3975 | + e.addElemName('\x08', name) |
3976 | + if v.Bool() { |
3977 | + e.addBytes(1) |
3978 | + } else { |
3979 | + e.addBytes(0) |
3980 | + } |
3981 | + |
3982 | + case reflect.Map: |
3983 | + e.addElemName('\x03', name) |
3984 | + e.addDoc(v) |
3985 | + |
3986 | + case reflect.Slice: |
3987 | + vt := v.Type() |
3988 | + et := vt.Elem() |
3989 | + if et.Kind() == reflect.Uint8 { |
3990 | + e.addElemName('\x05', name) |
3991 | + e.addBinary('\x00', v.Bytes()) |
3992 | + } else if et == typeDocElem { |
3993 | + e.addElemName('\x03', name) |
3994 | + e.addDoc(v) |
3995 | + } else { |
3996 | + e.addElemName('\x04', name) |
3997 | + e.addDoc(v) |
3998 | + } |
3999 | + |
4000 | + case reflect.Array: |
4001 | + et := v.Type().Elem() |
4002 | + if et.Kind() == reflect.Uint8 { |
4003 | + e.addElemName('\x05', name) |
4004 | + e.addBinary('\x00', v.Slice(0, v.Len()).Interface().([]byte)) |
4005 | + } else { |
4006 | + e.addElemName('\x04', name) |
4007 | + e.addDoc(v) |
4008 | + } |
4009 | + |
4010 | + case reflect.Struct: |
4011 | + switch s := v.Interface().(type) { |
4012 | + |
4013 | + case Raw: |
4014 | + kind := s.Kind |
4015 | + if kind == 0x00 { |
4016 | + kind = 0x03 |
4017 | + } |
4018 | + e.addElemName(kind, name) |
4019 | + e.addBytes(s.Data...) |
4020 | + |
4021 | + case Binary: |
4022 | + e.addElemName('\x05', name) |
4023 | + e.addBinary(s.Kind, s.Data) |
4024 | + |
4025 | + case RegEx: |
4026 | + e.addElemName('\x0B', name) |
4027 | + e.addCStr(s.Pattern) |
4028 | + e.addCStr(s.Options) |
4029 | + |
4030 | + case JavaScript: |
4031 | + if s.Scope == nil { |
4032 | + e.addElemName('\x0D', name) |
4033 | + e.addStr(s.Code) |
4034 | + } else { |
4035 | + e.addElemName('\x0F', name) |
4036 | + start := e.reserveInt32() |
4037 | + e.addStr(s.Code) |
4038 | + e.addDoc(reflect.ValueOf(s.Scope)) |
4039 | + e.setInt32(start, int32(len(e.out)-start)) |
4040 | + } |
4041 | + |
4042 | + case time.Time: |
4043 | + // MongoDB handles timestamps as milliseconds. |
4044 | + e.addElemName('\x09', name) |
4045 | + e.addInt64(s.Unix() * 1000 + int64(s.Nanosecond() / 1e6)) |
4046 | + |
4047 | + case url.URL: |
4048 | + e.addElemName('\x02', name) |
4049 | + e.addStr(s.String()) |
4050 | + |
4051 | + case undefined: |
4052 | + e.addElemName('\x06', name) |
4053 | + |
4054 | + default: |
4055 | + e.addElemName('\x03', name) |
4056 | + e.addDoc(v) |
4057 | + } |
4058 | + |
4059 | + default: |
4060 | + panic("Can't marshal " + v.Type().String() + " in a BSON document") |
4061 | + } |
4062 | +} |
4063 | + |
4064 | +// -------------------------------------------------------------------------- |
4065 | +// Marshaling of base types. |
4066 | + |
4067 | +func (e *encoder) addBinary(subtype byte, v []byte) { |
4068 | + if subtype == 0x02 { |
4069 | + // Wonder how that brilliant idea came to life. Obsolete, luckily. |
4070 | + e.addInt32(int32(len(v) + 4)) |
4071 | + e.addBytes(subtype) |
4072 | + e.addInt32(int32(len(v))) |
4073 | + } else { |
4074 | + e.addInt32(int32(len(v))) |
4075 | + e.addBytes(subtype) |
4076 | + } |
4077 | + e.addBytes(v...) |
4078 | +} |
4079 | + |
4080 | +func (e *encoder) addStr(v string) { |
4081 | + e.addInt32(int32(len(v) + 1)) |
4082 | + e.addCStr(v) |
4083 | +} |
4084 | + |
4085 | +func (e *encoder) addCStr(v string) { |
4086 | + e.addBytes([]byte(v)...) |
4087 | + e.addBytes(0) |
4088 | +} |
4089 | + |
4090 | +func (e *encoder) reserveInt32() (pos int) { |
4091 | + pos = len(e.out) |
4092 | + e.addBytes(0, 0, 0, 0) |
4093 | + return pos |
4094 | +} |
4095 | + |
4096 | +func (e *encoder) setInt32(pos int, v int32) { |
4097 | + e.out[pos+0] = byte(v) |
4098 | + e.out[pos+1] = byte(v >> 8) |
4099 | + e.out[pos+2] = byte(v >> 16) |
4100 | + e.out[pos+3] = byte(v >> 24) |
4101 | +} |
4102 | + |
4103 | +func (e *encoder) addInt32(v int32) { |
4104 | + u := uint32(v) |
4105 | + e.addBytes(byte(u), byte(u>>8), byte(u>>16), byte(u>>24)) |
4106 | +} |
4107 | + |
4108 | +func (e *encoder) addInt64(v int64) { |
4109 | + u := uint64(v) |
4110 | + e.addBytes(byte(u), byte(u>>8), byte(u>>16), byte(u>>24), |
4111 | + byte(u>>32), byte(u>>40), byte(u>>48), byte(u>>56)) |
4112 | +} |
4113 | + |
4114 | +func (e *encoder) addBytes(v ...byte) { |
4115 | + e.out = append(e.out, v...) |
4116 | +} |
4117 | |
4118 | === added file 'cluster.go' |
4119 | --- cluster.go 1970-01-01 00:00:00 +0000 |
4120 | +++ cluster.go 2013-04-03 09:16:27 +0000 |
4121 | @@ -0,0 +1,504 @@ |
4122 | +// mgo - MongoDB driver for Go |
4123 | +// |
4124 | +// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
4125 | +// |
4126 | +// All rights reserved. |
4127 | +// |
4128 | +// Redistribution and use in source and binary forms, with or without |
4129 | +// modification, are permitted provided that the following conditions are met: |
4130 | +// |
4131 | +// 1. Redistributions of source code must retain the above copyright notice, this |
4132 | +// list of conditions and the following disclaimer. |
4133 | +// 2. Redistributions in binary form must reproduce the above copyright notice, |
4134 | +// this list of conditions and the following disclaimer in the documentation |
4135 | +// and/or other materials provided with the distribution. |
4136 | +// |
4137 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
4138 | +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
4139 | +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
4140 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
4141 | +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
4142 | +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
4143 | +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
4144 | +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
4145 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
4146 | +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
4147 | + |
4148 | +package mgo |
4149 | + |
4150 | +import ( |
4151 | + "errors" |
4152 | + "sync" |
4153 | + "time" |
4154 | +) |
4155 | + |
4156 | +// --------------------------------------------------------------------------- |
4157 | +// Mongo cluster encapsulation. |
4158 | +// |
4159 | +// A cluster enables the communication with one or more servers participating |
4160 | +// in a mongo cluster. This works with individual servers, a replica set, |
4161 | +// a replica pair, one or multiple mongos routers, etc. |
4162 | + |
4163 | +type mongoCluster struct { |
4164 | + sync.RWMutex |
4165 | + serverSynced sync.Cond |
4166 | + userSeeds []string |
4167 | + dynaSeeds []string |
4168 | + servers mongoServers |
4169 | + masters mongoServers |
4170 | + references int |
4171 | + syncing bool |
4172 | + direct bool |
4173 | + cachedIndex map[string]bool |
4174 | + sync chan bool |
4175 | + dial dialer |
4176 | +} |
4177 | + |
4178 | +func newCluster(userSeeds []string, direct bool, dial dialer) *mongoCluster { |
4179 | + cluster := &mongoCluster{ |
4180 | + userSeeds: userSeeds, |
4181 | + references: 1, |
4182 | + direct: direct, |
4183 | + dial: dial, |
4184 | + } |
4185 | + cluster.serverSynced.L = cluster.RWMutex.RLocker() |
4186 | + cluster.sync = make(chan bool, 1) |
4187 | + go cluster.syncServersLoop() |
4188 | + return cluster |
4189 | +} |
4190 | + |
4191 | +// Acquire increases the reference count for the cluster. |
4192 | +func (cluster *mongoCluster) Acquire() { |
4193 | + cluster.Lock() |
4194 | + cluster.references++ |
4195 | + debugf("Cluster %p acquired (refs=%d)", cluster, cluster.references) |
4196 | + cluster.Unlock() |
4197 | +} |
4198 | + |
4199 | +// Release decreases the reference count for the cluster. Once |
4200 | +// it reaches zero, all servers will be closed. |
4201 | +func (cluster *mongoCluster) Release() { |
4202 | + cluster.Lock() |
4203 | + if cluster.references == 0 { |
4204 | + panic("cluster.Release() with references == 0") |
4205 | + } |
4206 | + cluster.references-- |
4207 | + debugf("Cluster %p released (refs=%d)", cluster, cluster.references) |
4208 | + if cluster.references == 0 { |
4209 | + for _, server := range cluster.servers.Slice() { |
4210 | + server.Close() |
4211 | + } |
4212 | + // Wake up the sync loop so it can die. |
4213 | + cluster.syncServers() |
4214 | + } |
4215 | + cluster.Unlock() |
4216 | +} |
4217 | + |
4218 | +func (cluster *mongoCluster) LiveServers() (servers []string) { |
4219 | + cluster.RLock() |
4220 | + for _, serv := range cluster.servers.Slice() { |
4221 | + servers = append(servers, serv.Addr) |
4222 | + } |
4223 | + cluster.RUnlock() |
4224 | + return servers |
4225 | +} |
4226 | + |
4227 | +func (cluster *mongoCluster) removeServer(server *mongoServer) { |
4228 | + cluster.Lock() |
4229 | + cluster.masters.Remove(server) |
4230 | + other := cluster.servers.Remove(server) |
4231 | + cluster.Unlock() |
4232 | + if other != nil { |
4233 | + other.Close() |
4234 | + log("Removed server ", server.Addr, " from cluster.") |
4235 | + } |
4236 | + server.Close() |
4237 | +} |
4238 | + |
4239 | +type isMasterResult struct { |
4240 | + IsMaster bool |
4241 | + Secondary bool |
4242 | + Primary string |
4243 | + Hosts []string |
4244 | + Passives []string |
4245 | +} |
4246 | + |
4247 | +func (cluster *mongoCluster) syncServer(server *mongoServer) (hosts []string, err error) { |
4248 | + addr := server.Addr |
4249 | + log("SYNC Processing ", addr, "...") |
4250 | + |
4251 | + var result isMasterResult |
4252 | + var tryerr error |
4253 | + for retry := 0; ; retry++ { |
4254 | + if retry == 3 { |
4255 | + return nil, tryerr |
4256 | + } |
4257 | + |
4258 | + socket, err := server.AcquireSocket(0) |
4259 | + if err != nil { |
4260 | + tryerr = err |
4261 | + logf("SYNC Failed to get socket to %s: %v", addr, err) |
4262 | + continue |
4263 | + } |
4264 | + |
4265 | + // Monotonic will let us talk to a slave and still hold the socket. |
4266 | + session := newSession(Monotonic, cluster, 10 * time.Second) |
4267 | + session.setSocket(socket) |
4268 | + |
4269 | + // session holds the socket now. |
4270 | + socket.Release() |
4271 | + |
4272 | + err = session.Run("ismaster", &result) |
4273 | + session.Close() |
4274 | + if err != nil { |
4275 | + tryerr = err |
4276 | + logf("SYNC Command 'ismaster' to %s failed: %v", addr, err) |
4277 | + continue |
4278 | + } |
4279 | + debugf("SYNC Result of 'ismaster' from %s: %#v", addr, result) |
4280 | + break |
4281 | + } |
4282 | + |
4283 | + if result.IsMaster { |
4284 | + debugf("SYNC %s is a master.", addr) |
4285 | + // Made an incorrect assumption above, so fix stats. |
4286 | + stats.conn(-1, false) |
4287 | + server.SetMaster(true) |
4288 | + stats.conn(+1, true) |
4289 | + } else if result.Secondary { |
4290 | + debugf("SYNC %s is a slave.", addr) |
4291 | + } else if cluster.direct { |
4292 | + logf("SYNC %s in unknown state. Pretending it's a slave due to direct connection.", addr) |
4293 | + } else { |
4294 | + logf("SYNC %s is neither a master nor a slave.", addr) |
4295 | + // Made an incorrect assumption above, so fix stats. |
4296 | + stats.conn(-1, false) |
4297 | + return nil, errors.New(addr + " is not a master nor slave") |
4298 | + } |
4299 | + |
4300 | + hosts = make([]string, 0, 1+len(result.Hosts)+len(result.Passives)) |
4301 | + if result.Primary != "" { |
4302 | + // First in the list to speed up master discovery. |
4303 | + hosts = append(hosts, result.Primary) |
4304 | + } |
4305 | + hosts = append(hosts, result.Hosts...) |
4306 | + hosts = append(hosts, result.Passives...) |
4307 | + |
4308 | + debugf("SYNC %s knows about the following peers: %#v", addr, hosts) |
4309 | + return hosts, nil |
4310 | +} |
4311 | + |
4312 | +func (cluster *mongoCluster) mergeServer(server *mongoServer) { |
4313 | + cluster.Lock() |
4314 | + previous := cluster.servers.Search(server) |
4315 | + isMaster := server.IsMaster() |
4316 | + if previous == nil { |
4317 | + cluster.servers.Add(server) |
4318 | + if isMaster { |
4319 | + cluster.masters.Add(server) |
4320 | + log("SYNC Adding ", server.Addr, " to cluster as a master.") |
4321 | + } else { |
4322 | + log("SYNC Adding ", server.Addr, " to cluster as a slave.") |
4323 | + } |
4324 | + } else { |
4325 | + if isMaster != previous.IsMaster() { |
4326 | + if isMaster { |
4327 | + log("SYNC Server ", server.Addr, " is now a master.") |
4328 | + cluster.masters.Add(previous) |
4329 | + } else { |
4330 | + log("SYNC Server ", server.Addr, " is now a slave.") |
4331 | + cluster.masters.Remove(previous) |
4332 | + } |
4333 | + } |
4334 | + previous.Merge(server) |
4335 | + } |
4336 | + debugf("SYNC Broadcasting availability of server %s", server.Addr) |
4337 | + cluster.serverSynced.Broadcast() |
4338 | + cluster.Unlock() |
4339 | +} |
4340 | + |
4341 | +func (cluster *mongoCluster) getKnownAddrs() []string { |
4342 | + cluster.RLock() |
4343 | + max := len(cluster.userSeeds) + len(cluster.dynaSeeds) + cluster.servers.Len() |
4344 | + seen := make(map[string]bool, max) |
4345 | + known := make([]string, 0, max) |
4346 | + |
4347 | + add := func(addr string) { |
4348 | + if _, found := seen[addr]; !found { |
4349 | + seen[addr] = true |
4350 | + known = append(known, addr) |
4351 | + } |
4352 | + } |
4353 | + |
4354 | + for _, addr := range cluster.userSeeds { |
4355 | + add(addr) |
4356 | + } |
4357 | + for _, addr := range cluster.dynaSeeds { |
4358 | + add(addr) |
4359 | + } |
4360 | + for _, serv := range cluster.servers.Slice() { |
4361 | + add(serv.Addr) |
4362 | + } |
4363 | + cluster.RUnlock() |
4364 | + |
4365 | + return known |
4366 | +} |
4367 | + |
4368 | +// syncServers injects a value into the cluster.sync channel to force |
4369 | +// an iteration of the syncServersLoop function. |
4370 | +func (cluster *mongoCluster) syncServers() { |
4371 | + select { |
4372 | + case cluster.sync <- true: |
4373 | + default: |
4374 | + } |
4375 | +} |
4376 | + |
4377 | +// How long to wait for a checkup of the cluster topology if nothing |
4378 | +// else kicks a synchronization before that. |
4379 | +const syncServersDelay = 3 * time.Minute |
4380 | + |
4381 | +// syncServersLoop loops while the cluster is alive to keep its idea of |
4382 | +// the server topology up-to-date. It must be called just once from |
4383 | +// newCluster. The loop iterates once syncServersDelay has passed, or |
4384 | +// if somebody injects a value into the cluster.sync channel to force a |
4385 | +// synchronization. A loop iteration will contact all servers in |
4386 | +// parallel, ask them about known peers and their own role within the |
4387 | +// cluster, and then attempt to do the same with all the peers |
4388 | +// retrieved. |
4389 | +func (cluster *mongoCluster) syncServersLoop() { |
4390 | + for { |
4391 | + debugf("SYNC Cluster %p is starting a sync loop iteration.", cluster) |
4392 | + |
4393 | + cluster.Lock() |
4394 | + if cluster.references == 0 { |
4395 | + cluster.Unlock() |
4396 | + break |
4397 | + } |
4398 | + cluster.references++ // Keep alive while syncing. |
4399 | + direct := cluster.direct |
4400 | + cluster.Unlock() |
4401 | + |
4402 | + cluster.syncServersIteration(direct) |
4403 | + |
4404 | + // We just synchronized, so consume any outstanding requests. |
4405 | + select { |
4406 | + case <-cluster.sync: |
4407 | + default: |
4408 | + } |
4409 | + |
4410 | + cluster.Release() |
4411 | + |
4412 | + // Hold off before allowing another sync. No point in |
4413 | + // burning CPU looking for down servers. |
4414 | + time.Sleep(5e8) |
4415 | + |
4416 | + cluster.Lock() |
4417 | + if cluster.references == 0 { |
4418 | + cluster.Unlock() |
4419 | + break |
4420 | + } |
4421 | + // Poke all waiters so they have a chance to timeout or |
4422 | + // restart syncing if they wish to. |
4423 | + cluster.serverSynced.Broadcast() |
4424 | + // Check if we have to restart immediately either way. |
4425 | + restart := !direct && cluster.masters.Empty() || cluster.servers.Empty() |
4426 | + cluster.Unlock() |
4427 | + |
4428 | + if restart { |
4429 | + log("SYNC No masters found. Will synchronize again.") |
4430 | + continue |
4431 | + } |
4432 | + |
4433 | + debugf("SYNC Cluster %p waiting for next requested or scheduled sync.", cluster) |
4434 | + |
4435 | + // Hold off until somebody explicitly requests a synchronization |
4436 | + // or it's time to check for a cluster topology change again. |
4437 | + select { |
4438 | + case <-cluster.sync: |
4439 | + case <-time.After(syncServersDelay): |
4440 | + } |
4441 | + } |
4442 | + debugf("SYNC Cluster %p is stopping its sync loop.", cluster) |
4443 | +} |
4444 | + |
4445 | +func (cluster *mongoCluster) syncServersIteration(direct bool) { |
4446 | + log("SYNC Starting full topology synchronization...") |
4447 | + |
4448 | + var wg sync.WaitGroup |
4449 | + var m sync.Mutex |
4450 | + mergePending := make(map[string]*mongoServer) |
4451 | + mergeRequested := make(map[string]bool) |
4452 | + seen := make(map[string]bool) |
4453 | + goodSync := false |
4454 | + |
4455 | + var spawnSync func(addr string, byMaster bool) |
4456 | + spawnSync = func(addr string, byMaster bool) { |
4457 | + wg.Add(1) |
4458 | + go func() { |
4459 | + defer wg.Done() |
4460 | + |
4461 | + server, err := newServer(addr, cluster.sync, cluster.dial) |
4462 | + if err != nil { |
4463 | + log("SYNC Failed to start sync of ", addr, ": ", err.Error()) |
4464 | + return |
4465 | + } |
4466 | + |
4467 | + m.Lock() |
4468 | + if byMaster { |
4469 | + if s, found := mergePending[server.ResolvedAddr]; found { |
4470 | + delete(mergePending, server.ResolvedAddr) |
4471 | + m.Unlock() |
4472 | + cluster.mergeServer(s) |
4473 | + return |
4474 | + } |
4475 | + mergeRequested[server.ResolvedAddr] = true |
4476 | + } |
4477 | + if seen[server.ResolvedAddr] { |
4478 | + m.Unlock() |
4479 | + return |
4480 | + } |
4481 | + seen[server.ResolvedAddr] = true |
4482 | + m.Unlock() |
4483 | + |
4484 | + hosts, err := cluster.syncServer(server) |
4485 | + if err == nil { |
4486 | + isMaster := server.IsMaster() |
4487 | + if !direct { |
4488 | + for _, addr := range hosts { |
4489 | + spawnSync(addr, isMaster) |
4490 | + } |
4491 | + } |
4492 | + |
4493 | + m.Lock() |
4494 | + merge := direct || isMaster |
4495 | + if mergeRequested[server.ResolvedAddr] { |
4496 | + merge = true |
4497 | + } else if !merge { |
4498 | + mergePending[server.ResolvedAddr] = server |
4499 | + } |
4500 | + if merge { |
4501 | + goodSync = true |
4502 | + } |
4503 | + m.Unlock() |
4504 | + if merge { |
4505 | + cluster.mergeServer(server) |
4506 | + } |
4507 | + } else { |
4508 | + server.Close() |
4509 | + } |
4510 | + }() |
4511 | + } |
4512 | + |
4513 | + for _, addr := range cluster.getKnownAddrs() { |
4514 | + spawnSync(addr, false) |
4515 | + } |
4516 | + wg.Wait() |
4517 | + |
4518 | + for _, server := range mergePending { |
4519 | + if goodSync { |
4520 | + cluster.removeServer(server) |
4521 | + } else { |
4522 | + server.Close() |
4523 | + } |
4524 | + } |
4525 | + |
4526 | + cluster.Lock() |
4527 | + ml := cluster.masters.Len() |
4528 | + logf("SYNC Synchronization completed: %d master(s) and %d slave(s) alive.", ml, cluster.servers.Len()-ml) |
4529 | + |
4530 | + // Update dynamic seeds, but only if we have any good servers. Otherwise, |
4531 | + // leave them alone for better chances of a successful sync in the future. |
4532 | + if goodSync { |
4533 | + dynaSeeds := make([]string, cluster.servers.Len()) |
4534 | + for i, server := range cluster.servers.Slice() { |
4535 | + dynaSeeds[i] = server.Addr |
4536 | + } |
4537 | + cluster.dynaSeeds = dynaSeeds |
4538 | + debugf("SYNC New dynamic seeds: %#v\n", dynaSeeds) |
4539 | + } |
4540 | + cluster.Unlock() |
4541 | +} |
4542 | + |
4543 | +var socketsPerServer = 4096 |
4544 | + |
4545 | +// AcquireSocket returns a socket to a server in the cluster. If slaveOk is |
4546 | +// true, it will attempt to return a socket to a slave server. If it is |
4547 | +// false, the socket will necessarily be to a master server. |
4548 | +func (cluster *mongoCluster) AcquireSocket(slaveOk bool, syncTimeout time.Duration) (s *mongoSocket, err error) { |
4549 | + var started time.Time |
4550 | + warnedLimit := false |
4551 | + for { |
4552 | + cluster.RLock() |
4553 | + for { |
4554 | + ml := cluster.masters.Len() |
4555 | + sl := cluster.servers.Len() |
4556 | + debugf("Cluster has %d known masters and %d known slaves.", ml, sl-ml) |
4557 | + if ml > 0 || slaveOk && sl > 0 { |
4558 | + break |
4559 | + } |
4560 | + if started.IsZero() { |
4561 | + started = time.Now() // Initialize after fast path above. |
4562 | + } else if syncTimeout != 0 && started.Before(time.Now().Add(-syncTimeout)) { |
4563 | + cluster.RUnlock() |
4564 | + return nil, errors.New("no reachable servers") |
4565 | + } |
4566 | + log("Waiting for servers to synchronize...") |
4567 | + cluster.syncServers() |
4568 | + |
4569 | + // Remember: this will release and reacquire the lock. |
4570 | + cluster.serverSynced.Wait() |
4571 | + } |
4572 | + |
4573 | + var server *mongoServer |
4574 | + if slaveOk { |
4575 | + server = cluster.servers.MostAvailable() |
4576 | + } else { |
4577 | + server = cluster.masters.MostAvailable() |
4578 | + } |
4579 | + cluster.RUnlock() |
4580 | + |
4581 | + s, err = server.AcquireSocket(socketsPerServer) |
4582 | + if err == errSocketLimit { |
4583 | + if !warnedLimit { |
4584 | + log("WARNING: Per-server connection limit reached.") |
4585 | + } |
4586 | + time.Sleep(1e8) |
4587 | + continue |
4588 | + } |
4589 | + if err != nil { |
4590 | + cluster.removeServer(server) |
4591 | + cluster.syncServers() |
4592 | + continue |
4593 | + } |
4594 | + return s, nil |
4595 | + } |
4596 | + panic("unreached") |
4597 | +} |
4598 | + |
4599 | +func (cluster *mongoCluster) CacheIndex(cacheKey string, exists bool) { |
4600 | + cluster.Lock() |
4601 | + if cluster.cachedIndex == nil { |
4602 | + cluster.cachedIndex = make(map[string]bool) |
4603 | + } |
4604 | + if exists { |
4605 | + cluster.cachedIndex[cacheKey] = true |
4606 | + } else { |
4607 | + delete(cluster.cachedIndex, cacheKey) |
4608 | + } |
4609 | + cluster.Unlock() |
4610 | +} |
4611 | + |
4612 | +func (cluster *mongoCluster) HasCachedIndex(cacheKey string) (result bool) { |
4613 | + cluster.RLock() |
4614 | + if cluster.cachedIndex != nil { |
4615 | + result = cluster.cachedIndex[cacheKey] |
4616 | + } |
4617 | + cluster.RUnlock() |
4618 | + return |
4619 | +} |
4620 | + |
4621 | +func (cluster *mongoCluster) ResetIndexCache() { |
4622 | + cluster.Lock() |
4623 | + cluster.cachedIndex = make(map[string]bool) |
4624 | + cluster.Unlock() |
4625 | +} |
4626 | |
4627 | === added file 'cluster_test.go' |
4628 | --- cluster_test.go 1970-01-01 00:00:00 +0000 |
4629 | +++ cluster_test.go 2013-04-03 09:16:27 +0000 |
4630 | @@ -0,0 +1,1108 @@ |
4631 | +// mgo - MongoDB driver for Go |
4632 | +// |
4633 | +// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> |
4634 | +// |
4635 | +// All rights reserved. |
4636 | +// |
4637 | +// Redistribution and use in source and binary forms, with or without |
4638 | +// modification, are permitted provided that the following conditions are met: |
4639 | +// |
4640 | +// 1. Redistributions of source code must retain the above copyright notice, this |
4641 | +// list of conditions and the following disclaimer. |
4642 | +// 2. Redistributions in binary form must reproduce the above copyright notice, |
4643 | +// this list of conditions and the following disclaimer in the documentation |
4644 | +// and/or other materials provided with the distribution. |
4645 | +// |
4646 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
4647 | +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
4648 | +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
4649 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
4650 | +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
4651 | +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
4652 | +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
4653 | +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
4654 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
4655 | +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
4656 | + |
4657 | +package mgo_test |
4658 | + |
4659 | +import ( |
4660 | + "fmt" |
4661 | + "io" |
4662 | + . "launchpad.net/gocheck" |
4663 | + "labix.org/v2/mgo" |
4664 | + "labix.org/v2/mgo/bson" |
4665 | + "net" |
4666 | + "strings" |
4667 | + "time" |
4668 | +) |
4669 | + |
4670 | +func (s *S) TestNewSession(c *C) { |
4671 | + session, err := mgo.Dial("localhost:40001") |
4672 | + c.Assert(err, IsNil) |
4673 | + defer session.Close() |
4674 | + |
4675 | + // Do a dummy operation to wait for connection. |
4676 | + coll := session.DB("mydb").C("mycoll") |
4677 | + err = coll.Insert(M{"_id": 1}) |
4678 | + c.Assert(err, IsNil) |
4679 | + |
4680 | + // Tweak safety and query settings to ensure other has copied those. |
4681 | + session.SetSafe(nil) |
4682 | + session.SetBatch(-1) |
4683 | + other := session.New() |
4684 | + defer other.Close() |
4685 | + session.SetSafe(&mgo.Safe{}) |
4686 | + |
4687 | + // Clone was copied while session was unsafe, so no errors. |
4688 | + otherColl := other.DB("mydb").C("mycoll") |
4689 | + err = otherColl.Insert(M{"_id": 1}) |
4690 | + c.Assert(err, IsNil) |
4691 | + |
4692 | + // Original session was made safe again. |
4693 | + err = coll.Insert(M{"_id": 1}) |
4694 | + c.Assert(err, NotNil) |
4695 | + |
4696 | + // With New(), each session has its own socket now. |
4697 | + stats := mgo.GetStats() |
4698 | + c.Assert(stats.MasterConns, Equals, 2) |
4699 | + c.Assert(stats.SocketsInUse, Equals, 2) |
4700 | + |
4701 | + // Ensure query parameters were cloned. |
4702 | + err = otherColl.Insert(M{"_id": 2}) |
4703 | + c.Assert(err, IsNil) |
4704 | + |
4705 | + // Ping the database to ensure the nonce has been received already. |
4706 | + c.Assert(other.Ping(), IsNil) |
4707 | + |
4708 | + mgo.ResetStats() |
4709 | + |
4710 | + iter := otherColl.Find(M{}).Iter() |
4711 | + c.Assert(err, IsNil) |
4712 | + |
4713 | + m := M{} |
4714 | + ok := iter.Next(m) |
4715 | + c.Assert(ok, Equals, true) |
4716 | + err = iter.Err() |
4717 | + c.Assert(err, IsNil) |
4718 | + |
4719 | + // If Batch(-1) is in effect, a single document must have been received. |
4720 | + stats = mgo.GetStats() |
4721 | + c.Assert(stats.ReceivedDocs, Equals, 1) |
4722 | +} |
4723 | + |
4724 | +func (s *S) TestCloneSession(c *C) { |
4725 | + session, err := mgo.Dial("localhost:40001") |
4726 | + c.Assert(err, IsNil) |
4727 | + defer session.Close() |
4728 | + |
4729 | + // Do a dummy operation to wait for connection. |
4730 | + coll := session.DB("mydb").C("mycoll") |
4731 | + err = coll.Insert(M{"_id": 1}) |
4732 | + c.Assert(err, IsNil) |
4733 | + |
4734 | + // Tweak safety and query settings to ensure clone is copying those. |
4735 | + session.SetSafe(nil) |
4736 | + session.SetBatch(-1) |
4737 | + clone := session.Clone() |
4738 | + defer clone.Close() |
4739 | + session.SetSafe(&mgo.Safe{}) |
4740 | + |
4741 | + // Clone was copied while session was unsafe, so no errors. |
4742 | + cloneColl := clone.DB("mydb").C("mycoll") |
4743 | + err = cloneColl.Insert(M{"_id": 1}) |
4744 | + c.Assert(err, IsNil) |
4745 | + |
4746 | + // Original session was made safe again. |
4747 | + err = coll.Insert(M{"_id": 1}) |
4748 | + c.Assert(err, NotNil) |
4749 | + |
4750 | + // With Clone(), same socket is shared between sessions now. |
4751 | + stats := mgo.GetStats() |
4752 | + c.Assert(stats.SocketsInUse, Equals, 1) |
4753 | + c.Assert(stats.SocketRefs, Equals, 2) |
4754 | + |
4755 | + // Refreshing one of them should let the original socket go, |
4756 | + // while preserving the safety settings. |
4757 | + clone.Refresh() |
4758 | + err = cloneColl.Insert(M{"_id": 1}) |
4759 | + c.Assert(err, IsNil) |
4760 | + |
4761 | + // Must have used another connection now. |
4762 | + stats = mgo.GetStats() |
4763 | + c.Assert(stats.SocketsInUse, Equals, 2) |
4764 | + c.Assert(stats.SocketRefs, Equals, 2) |
4765 | + |
4766 | + // Ensure query parameters were cloned. |
4767 | + err = cloneColl.Insert(M{"_id": 2}) |
4768 | + c.Assert(err, IsNil) |
4769 | + |
4770 | + // Ping the database to ensure the nonce has been received already. |
4771 | + c.Assert(clone.Ping(), IsNil) |
4772 | + |
4773 | + mgo.ResetStats() |
4774 | + |
4775 | + iter := cloneColl.Find(M{}).Iter() |
4776 | + c.Assert(err, IsNil) |
4777 | + |
4778 | + m := M{} |
4779 | + ok := iter.Next(m) |
4780 | + c.Assert(ok, Equals, true) |
4781 | + err = iter.Err() |
4782 | + c.Assert(err, IsNil) |
4783 | + |
4784 | + // If Batch(-1) is in effect, a single document must have been received. |
4785 | + stats = mgo.GetStats() |
4786 | + c.Assert(stats.ReceivedDocs, Equals, 1) |
4787 | +} |
4788 | + |
4789 | +func (s *S) TestSetModeStrong(c *C) { |
4790 | + session, err := mgo.Dial("localhost:40012") |
4791 | + c.Assert(err, IsNil) |
4792 | + defer session.Close() |
4793 | + |
4794 | + session.SetMode(mgo.Monotonic, false) |
4795 | + session.SetMode(mgo.Strong, false) |
4796 | + |
4797 | + c.Assert(session.Mode(), Equals, mgo.Strong) |
4798 | + |
4799 | + result := M{} |
4800 | + cmd := session.DB("admin").C("$cmd") |
4801 | + err = cmd.Find(M{"ismaster": 1}).One(&result) |
4802 | + c.Assert(err, IsNil) |
4803 | + c.Assert(result["ismaster"], Equals, true) |
4804 | + |
4805 | + coll := session.DB("mydb").C("mycoll") |
4806 | + err = coll.Insert(M{"a": 1}) |
4807 | + c.Assert(err, IsNil) |
4808 | + |
4809 | + // Wait since the sync also uses sockets. |
4810 | + for len(session.LiveServers()) != 3 { |
4811 | + c.Log("Waiting for cluster sync to finish...") |
4812 | + time.Sleep(5e8) |
4813 | + } |
4814 | + |
4815 | + stats := mgo.GetStats() |
4816 | + c.Assert(stats.MasterConns, Equals, 1) |
4817 | + c.Assert(stats.SlaveConns, Equals, 2) |
4818 | + c.Assert(stats.SocketsInUse, Equals, 1) |
4819 | + |
4820 | + session.SetMode(mgo.Strong, true) |
4821 | + |
4822 | + stats = mgo.GetStats() |
4823 | + c.Assert(stats.SocketsInUse, Equals, 0) |
4824 | +} |
4825 | + |
4826 | +func (s *S) TestSetModeMonotonic(c *C) { |
4827 | + // Must necessarily connect to a slave, otherwise the |
4828 | + // master connection will be available first. |
4829 | + session, err := mgo.Dial("localhost:40012") |
4830 | + c.Assert(err, IsNil) |
4831 | + defer session.Close() |
4832 | + |
4833 | + session.SetMode(mgo.Monotonic, false) |
4834 | + |
4835 | + c.Assert(session.Mode(), Equals, mgo.Monotonic) |
4836 | + |
4837 | + result := M{} |
4838 | + cmd := session.DB("admin").C("$cmd") |
4839 | + err = cmd.Find(M{"ismaster": 1}).One(&result) |
4840 | + c.Assert(err, IsNil) |
4841 | + c.Assert(result["ismaster"], Equals, false) |
4842 | + |
4843 | + coll := session.DB("mydb").C("mycoll") |
4844 | + err = coll.Insert(M{"a": 1}) |
4845 | + c.Assert(err, IsNil) |
4846 | + |
4847 | + result = M{} |
4848 | + err = cmd.Find(M{"ismaster": 1}).One(&result) |
4849 | + c.Assert(err, IsNil) |
4850 | + c.Assert(result["ismaster"], Equals, true) |
4851 | + |
4852 | + // Wait since the sync also uses sockets. |
4853 | + for len(session.LiveServers()) != 3 { |
4854 | + c.Log("Waiting for cluster sync to finish...") |
4855 | + time.Sleep(5e8) |
4856 | + } |
4857 | + |
4858 | + stats := mgo.GetStats() |
4859 | + c.Assert(stats.MasterConns, Equals, 1) |
4860 | + c.Assert(stats.SlaveConns, Equals, 2) |
4861 | + c.Assert(stats.SocketsInUse, Equals, 2) |
4862 | + |
4863 | + session.SetMode(mgo.Monotonic, true) |
4864 | + |
4865 | + stats = mgo.GetStats() |
4866 | + c.Assert(stats.SocketsInUse, Equals, 0) |
4867 | +} |
4868 | + |
4869 | +func (s *S) TestSetModeMonotonicAfterStrong(c *C) { |
4870 | + // Test that a strong session shifting to a monotonic |
4871 | + // one preserves the socket untouched. |
4872 | + |
4873 | + session, err := mgo.Dial("localhost:40012") |
4874 | + c.Assert(err, IsNil) |
4875 | + defer session.Close() |
4876 | + |
4877 | + // Insert something to force a connection to the master. |
4878 | + coll := session.DB("mydb").C("mycoll") |
4879 | + err = coll.Insert(M{"a": 1}) |
4880 | + c.Assert(err, IsNil) |
4881 | + |
4882 | + session.SetMode(mgo.Monotonic, false) |
4883 | + |
4884 | + // Wait since the sync also uses sockets. |
4885 | + for len(session.LiveServers()) != 3 { |
4886 | + c.Log("Waiting for cluster sync to finish...") |
4887 | + time.Sleep(5e8) |
4888 | + } |
4889 | + |
4890 | + // Master socket should still be reserved. |
4891 | + stats := mgo.GetStats() |
4892 | + c.Assert(stats.SocketsInUse, Equals, 1) |
4893 | + |
4894 | + // Confirm it's the master even though it's Monotonic by now. |
4895 | + result := M{} |
4896 | + cmd := session.DB("admin").C("$cmd") |
4897 | + err = cmd.Find(M{"ismaster": 1}).One(&result) |
4898 | + c.Assert(err, IsNil) |
4899 | + c.Assert(result["ismaster"], Equals, true) |
4900 | +} |
4901 | + |
4902 | +func (s *S) TestSetModeStrongAfterMonotonic(c *C) { |
4903 | + // Test that shifting from Monotonic to Strong while |
4904 | + // using a slave socket will keep the socket reserved |
4905 | + // until the master socket is necessary, so that no |
4906 | + // switch over occurs unless it's actually necessary. |
4907 | + |
4908 | + // Must necessarily connect to a slave, otherwise the |
4909 | + // master connection will be available first. |
4910 | + session, err := mgo.Dial("localhost:40012") |
4911 | + c.Assert(err, IsNil) |
4912 | + defer session.Close() |
4913 | + |
4914 | + session.SetMode(mgo.Monotonic, false) |
4915 | + |
4916 | + // Ensure we're talking to a slave, and reserve the socket. |
4917 | + result := M{} |
4918 | + err = session.Run("ismaster", &result) |
4919 | + c.Assert(err, IsNil) |
4920 | + c.Assert(result["ismaster"], Equals, false) |
4921 | + |
4922 | + // Switch to a Strong session. |
4923 | + session.SetMode(mgo.Strong, false) |
4924 | + |
4925 | + // Wait since the sync also uses sockets. |
4926 | + for len(session.LiveServers()) != 3 { |
4927 | + c.Log("Waiting for cluster sync to finish...") |
4928 | + time.Sleep(5e8) |
4929 | + } |
4930 | + |
4931 | + // Slave socket should still be reserved. |
4932 | + stats := mgo.GetStats() |
4933 | + c.Assert(stats.SocketsInUse, Equals, 1) |
4934 | + |
4935 | + // But any operation will switch it to the master. |
4936 | + result = M{} |
4937 | + err = session.Run("ismaster", &result) |
4938 | + c.Assert(err, IsNil) |
4939 | + c.Assert(result["ismaster"], Equals, true) |
4940 | +} |
4941 | + |
4942 | +func (s *S) TestSetModeMonotonicWriteOnIteration(c *C) { |
4943 | + // Must necessarily connect to a slave, otherwise the |
4944 | + // master connection will be available first. |
4945 | + session, err := mgo.Dial("localhost:40012") |
4946 | + c.Assert(err, IsNil) |
4947 | + defer session.Close() |
4948 | + |
4949 | + session.SetMode(mgo.Monotonic, false) |
4950 | + |
4951 | + c.Assert(session.Mode(), Equals, mgo.Monotonic) |
4952 | + |
4953 | + coll1 := session.DB("mydb").C("mycoll1") |
4954 | + coll2 := session.DB("mydb").C("mycoll2") |
4955 | + |
4956 | + ns := []int{40, 41, 42, 43, 44, 45, 46} |
4957 | + for _, n := range ns { |
4958 | + err := coll1.Insert(M{"n": n}) |
4959 | + c.Assert(err, IsNil) |
4960 | + } |
4961 | + |
4962 | + // Release master so we can grab a slave again. |
4963 | + session.Refresh() |
4964 | + |
4965 | + // Wait until synchronization is done. |
4966 | + for { |
4967 | + n, err := coll1.Count() |
4968 | + c.Assert(err, IsNil) |
4969 | + if n == len(ns) { |
4970 | + break |
4971 | + } |
4972 | + } |
4973 | + |
4974 | + iter := coll1.Find(nil).Batch(2).Iter() |
4975 | + i := 0 |
4976 | + m := M{} |
4977 | + for iter.Next(&m) { |
4978 | + i++ |
4979 | + if i > 3 { |
4980 | + err := coll2.Insert(M{"n": 47 + i}) |
4981 | + c.Assert(err, IsNil) |
4982 | + } |
4983 | + } |
4984 | + c.Assert(i, Equals, len(ns)) |
4985 | +} |
4986 | + |
4987 | +func (s *S) TestSetModeEventual(c *C) { |
4988 | + // Must necessarily connect to a slave, otherwise the |
4989 | + // master connection will be available first. |
4990 | + session, err := mgo.Dial("localhost:40012") |
4991 | + c.Assert(err, IsNil) |
4992 | + defer session.Close() |
4993 | + |
4994 | + session.SetMode(mgo.Eventual, false) |
4995 | + |
4996 | + c.Assert(session.Mode(), Equals, mgo.Eventual) |
4997 | + |
4998 | + result := M{} |
4999 | + err = session.Run("ismaster", &result) |
5000 | + c.Assert(err, IsNil) |